Shape sharing for Skylark providers.

Add CompactSkylarkInfo, which stores its values as an array instead of
a map. The space savings will probably not be dramatic because
providers usually have a limited amount of keys. But, there are a lot
of them!

Change-Id: Idd452a5e3982f773b1c5202c73f3d7031ec022c6
PiperOrigin-RevId: 176995376
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java
new file mode 100644
index 0000000..a0d546f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java
@@ -0,0 +1,124 @@
+// Copyright 2017 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 static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.SkylarkInfo.CompactSkylarkInfo;
+import com.google.devtools.build.lib.packages.SkylarkInfo.MapBackedSkylarkInfo;
+import com.google.devtools.build.lib.syntax.Concatable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link SkylarkInfo} and its subclasses. */
+@RunWith(JUnit4.class)
+public class SkylarkInfoTest {
+
+  @Test
+  public void sameProviderDifferentLayoutConcatenation() throws Exception {
+    SkylarkProvider provider =
+        new SkylarkProvider("provider", ImmutableList.of("f1", "f2"), Location.BUILTIN);
+    ImmutableMap<String, Integer> layout1 = ImmutableMap.of("f1", 0, "f2", 1);
+    ImmutableMap<String, Integer> layout2 = ImmutableMap.of("f1", 1, "f2", 0);
+    CompactSkylarkInfo info1 =
+        new CompactSkylarkInfo(provider, layout1, new Object[] {5, null}, Location.BUILTIN);
+    CompactSkylarkInfo info2 =
+        new CompactSkylarkInfo(provider, layout2, new Object[] {4, null}, Location.BUILTIN);
+    SkylarkInfo result = (SkylarkInfo) info1.getConcatter().concat(info1, info2, Location.BUILTIN);
+    assertThat(result).isInstanceOf(MapBackedSkylarkInfo.class);
+    assertThat(result.getValue("f1")).isEqualTo(5);
+    assertThat(result.getValue("f2")).isEqualTo(4);
+  }
+
+  @Test
+  public void immutabilityPredicate() throws Exception {
+    SkylarkProvider provider =
+        new SkylarkProvider("provider", ImmutableList.of("f1", "f2"), Location.BUILTIN);
+    ImmutableMap<String, Integer> layout = ImmutableMap.of("f1", 0, "f2", 1);
+    SkylarkInfo compactInfo =
+        new CompactSkylarkInfo(provider, layout, new Object[] {5, null}, Location.BUILTIN);
+    assertThat(compactInfo.isImmutable()).isFalse();
+    SkylarkInfo mapInfo =
+        new MapBackedSkylarkInfo(provider, ImmutableMap.of("f1", 5), Location.BUILTIN);
+    assertThat(mapInfo.isImmutable()).isFalse();
+    provider.export(Label.create("package", "target"), "provider");
+    assertThat(compactInfo.isImmutable()).isTrue();
+    assertThat(mapInfo.isImmutable()).isTrue();
+    compactInfo =
+        new CompactSkylarkInfo(provider, layout, new Object[] {5, new Object()}, Location.BUILTIN);
+    assertThat(compactInfo.isImmutable()).isFalse();
+    mapInfo =
+        new MapBackedSkylarkInfo(
+            provider, ImmutableMap.of("f1", 5, "f2", new Object()), Location.BUILTIN);
+    assertThat(mapInfo.isImmutable()).isFalse();
+  }
+
+  @Test
+  public void equality() throws Exception {
+    Provider provider1 =
+        new SkylarkProvider("provider1", ImmutableList.of("f1", "f2"), Location.BUILTIN);
+    Provider provider2 =
+        new SkylarkProvider("provider2", ImmutableList.of("f1", "f2"), Location.BUILTIN);
+    ImmutableMap<String, Integer> layout = ImmutableMap.of("f1", 0, "f2", 1);
+    new EqualsTester()
+        .addEqualityGroup(
+            new CompactSkylarkInfo(provider1, layout, new Object[] {4, null}, Location.BUILTIN),
+            new MapBackedSkylarkInfo(provider1, ImmutableMap.of("f1", 4), Location.BUILTIN))
+        .addEqualityGroup(
+            new CompactSkylarkInfo(provider2, layout, new Object[] {4, null}, Location.BUILTIN),
+            new MapBackedSkylarkInfo(provider2, ImmutableMap.of("f1", 4), Location.BUILTIN))
+        .addEqualityGroup(
+            new CompactSkylarkInfo(provider1, layout, new Object[] {4, 5}, Location.BUILTIN),
+            new MapBackedSkylarkInfo(
+                provider1, ImmutableMap.of("f1", 4, "f2", 5), Location.BUILTIN))
+        .testEquals();
+  }
+
+  @Test
+  public void heterogeneousConcatenation() throws Exception {
+    Provider provider =
+        new SkylarkProvider("provider", ImmutableList.of("f1", "f2"), Location.BUILTIN);
+    ImmutableMap<String, Integer> layout = ImmutableMap.of("f1", 0, "f2", 1);
+    SkylarkInfo p1 = new MapBackedSkylarkInfo(provider, ImmutableMap.of("f1", 4), Location.BUILTIN);
+    CompactSkylarkInfo p2 =
+        new CompactSkylarkInfo(provider, layout, new Object[] {null, 5}, Location.BUILTIN);
+    Concatable result = p1.getConcatter().concat(p1, p2, Location.BUILTIN);
+    assertThat(result).isInstanceOf(MapBackedSkylarkInfo.class);
+    assertThat(((SkylarkInfo) result).getKeys()).containsExactly("f1", "f2");
+    assertThat(((SkylarkInfo) result).getValue("f1")).isEqualTo(4);
+    assertThat(((SkylarkInfo) result).getValue("f2")).isEqualTo(5);
+  }
+
+  @Test
+  public void compactConcatenationReturnsCompact() throws Exception {
+    Provider provider =
+        new SkylarkProvider("provider", ImmutableList.of("f1", "f2"), Location.BUILTIN);
+    ImmutableMap<String, Integer> layout = ImmutableMap.of("f1", 0, "f2", 1);
+    CompactSkylarkInfo p1 =
+        new CompactSkylarkInfo(provider, layout, new Object[] {4, null}, Location.BUILTIN);
+    CompactSkylarkInfo p2 =
+        new CompactSkylarkInfo(provider, layout, new Object[] {null, 5}, Location.BUILTIN);
+    Concatable result = p1.getConcatter().concat(p1, p2, Location.BUILTIN);
+    assertThat(result).isInstanceOf(CompactSkylarkInfo.class);
+    assertThat(((CompactSkylarkInfo) result).getKeys()).containsExactly("f1", "f2");
+    assertThat(((CompactSkylarkInfo) result).getValue("f1")).isEqualTo(4);
+    assertThat(((CompactSkylarkInfo) result).getValue("f2")).isEqualTo(5);
+  }
+}
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 bbf3c5b..ccf7309 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
@@ -1265,6 +1265,39 @@
   }
 
   @Test
+  public void declaredProvidersWithFieldsConcatSuccess() throws Exception {
+    evalAndExport(
+        "data = provider(fields=['f1', 'f2'])",
+        "d1 = data(f1 = 4)",
+        "d2 = data(f2 = 5)",
+        "d3 = d1 + d2",
+        "f1 = d3.f1",
+        "f2 = d3.f2");
+    assertThat(lookup("f1")).isEqualTo(4);
+    assertThat(lookup("f2")).isEqualTo(5);
+  }
+
+  @Test
+  public void declaredProvidersWithFieldsConcatError() throws Exception {
+    evalAndExport("data1 = provider(fields=['f1', 'f2'])", "data2 = provider(fields=['f3'])");
+    checkEvalError(
+        "Cannot concat data1 with data2",
+        "d1 = data1(f1=1, f2=2)",
+        "d2 = data2(f3=3)",
+        "d = d1 + d2");
+  }
+
+  @Test
+  public void declaredProvidersWithOverlappingFieldsConcatError() throws Exception {
+    evalAndExport("data = provider(fields=['f1', 'f2'])");
+    checkEvalError(
+        "Cannot concat structs with common field(s): f1",
+        "d1 = data(f1 = 4)",
+        "d2 = data(f1 = 5)",
+        "d1 + d2");
+  }
+
+  @Test
   public void structsAsDeclaredProvidersTest() throws Exception {
     evalAndExport(
         "data = struct(x = 1)"