Make SkylarkClassObject "Bazel-specific".

This in preparation to DeclaredProviders implementation.

--
MOS_MIGRATED_REVID=129420617
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
index ad2290a..f8d10cf 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
 import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction.RepositoryFunctionException;
 import com.google.devtools.build.lib.skyframe.FileSymlinkException;
 import com.google.devtools.build.lib.skyframe.FileValue;
@@ -33,7 +34,6 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Runtime;
 import com.google.devtools.build.lib.syntax.SkylarkType;
@@ -46,7 +46,6 @@
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 import com.google.devtools.build.skyframe.SkyKey;
-
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index c53b92a..9088840 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -29,7 +29,6 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
 import com.google.devtools.build.lib.syntax.ClassObject;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
 import com.google.devtools.build.lib.syntax.Type;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java b/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java
index c20cf3b..3789091 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java
@@ -27,7 +27,6 @@
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.syntax.ClassObject;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Runtime;
 import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
@@ -35,7 +34,6 @@
 import com.google.devtools.build.lib.util.StringUtil;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -44,7 +42,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import javax.annotation.Nullable;
 
 /**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 4c3138a..7b8e57f 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -1526,7 +1526,7 @@
         builder.put(function.getName(), function);
       }
     }
-    return new ClassObject.SkylarkClassObject(builder.build(), "no native function or rule '%s'");
+    return new SkylarkClassObject(builder.build(), "no native function or rule '%s'");
   }
 
   private void buildPkgEnv(Environment pkgEnv, PackageContext context, RuleFactory ruleFactory) {
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
new file mode 100644
index 0000000..d28146a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
@@ -0,0 +1,184 @@
+// 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.common.base.Joiner;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import com.google.devtools.build.lib.events.Location;
+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.ClassObject;
+import com.google.devtools.build.lib.syntax.Concatable;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.io.Serializable;
+import java.util.Map;
+
+/** An implementation class of ClassObject for structs created in Skylark code. */
+@SkylarkModule(
+  name = "struct",
+  category = SkylarkModuleCategory.BUILTIN,
+  doc =
+      "A special language element to support structs (i.e. simple value objects). "
+          + "See the global <a href=\"globals.html#struct\">struct</a> function "
+          + "for more details."
+)
+public class SkylarkClassObject implements ClassObject, SkylarkValue, Concatable, Serializable {
+  /** Error message to use when errorMessage argument is null. */
+  private static final String DEFAULT_ERROR_MESSAGE = "'struct' object has no attribute '%s'";
+
+  private final ImmutableMap<String, Object> values;
+  private final Location creationLoc;
+  private final String errorMessage;
+
+  /**
+   * Primarily for testing purposes where no location is available and the default
+   * errorMessage suffices.
+   */
+  public SkylarkClassObject(Map<String, Object> values) {
+    this.values = copyValues(values);
+    this.creationLoc = null;
+    this.errorMessage = DEFAULT_ERROR_MESSAGE;
+  }
+
+  /**
+   * Creates a built-in struct (i.e. without creation loc). The errorMessage has to have
+   * exactly one '%s' parameter to substitute the struct field name.
+   */
+  public SkylarkClassObject(Map<String, Object> values, String errorMessage) {
+    this.values = copyValues(values);
+    this.creationLoc = null;
+    this.errorMessage = Preconditions.checkNotNull(errorMessage);
+  }
+
+  public SkylarkClassObject(Map<String, Object> values, Location creationLoc) {
+    this.values = copyValues(values);
+    this.creationLoc = Preconditions.checkNotNull(creationLoc);
+    this.errorMessage = DEFAULT_ERROR_MESSAGE;
+  }
+
+  // Ensure that values are all acceptable to Skylark before to stuff them in a ClassObject
+  private ImmutableMap<String, Object> copyValues(Map<String, Object> values) {
+    ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
+    for (Map.Entry<String, Object> e : values.entrySet()) {
+      builder.put(e.getKey(), SkylarkType.convertToSkylark(e.getValue(), null));
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Object getValue(String name) {
+    return values.get(name);
+  }
+
+  /**
+   *  Returns a value and try to cast it into specified type
+   */
+  public <TYPE> TYPE getValue(String key, Class<TYPE> type) throws EvalException {
+    Object obj = values.get(key);
+    if (obj == null) {
+      return null;
+    }
+    SkylarkType.checkType(obj, type, key);
+    return type.cast(obj);
+  }
+
+  @Override
+  public ImmutableCollection<String> getKeys() {
+    return values.keySet();
+  }
+
+  public Location getCreationLoc() {
+    return Preconditions.checkNotNull(creationLoc,
+        "This struct was not created in a Skylark code");
+  }
+
+  @Override
+  public Concatter getConcatter() {
+    return StructConcatter.INSTANCE;
+  }
+
+  private static class StructConcatter implements Concatter {
+    private static final StructConcatter INSTANCE = new StructConcatter();
+
+    private StructConcatter() {}
+
+    @Override
+    public SkylarkClassObject concat(
+        Concatable left, Concatable right, Location loc) throws EvalException {
+      SkylarkClassObject lval = (SkylarkClassObject) left;
+      SkylarkClassObject rval = (SkylarkClassObject) right;
+      SetView<String> commonFields = Sets
+          .intersection(lval.values.keySet(), rval.values.keySet());
+      if (!commonFields.isEmpty()) {
+        throw new EvalException(loc, "Cannot concat structs with common field(s): "
+            + Joiner.on(",").join(commonFields));
+      }
+      return new SkylarkClassObject(ImmutableMap.<String, Object>builder()
+          .putAll(lval.values).putAll(rval.values).build(), loc);
+    }
+  }
+
+  @Override
+  public String errorMessage(String name) {
+    String suffix =
+        "Available attributes: "
+            + Joiner.on(", ").join(Ordering.natural().sortedCopy(values.keySet()));
+    return String.format(errorMessage, name) + "\n" + suffix;
+  }
+
+  @Override
+  public boolean isImmutable() {
+    for (Object item : values.values()) {
+      if (!EvalUtils.isImmutable(item)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Convert the object to string using Skylark syntax. The output tries to be
+   * reversible (but there is no guarantee, it depends on the actual values).
+   */
+  @Override
+  public void write(Appendable buffer, char quotationMark) {
+    boolean first = true;
+    Printer.append(buffer, "struct(");
+    // Sort by key to ensure deterministic output.
+    for (String key : Ordering.natural().sortedCopy(values.keySet())) {
+      if (!first) {
+        Printer.append(buffer, ", ");
+      }
+      first = false;
+      Printer.append(buffer, key);
+      Printer.append(buffer, " = ");
+      Printer.write(buffer, values.get(key), quotationMark);
+    }
+    Printer.append(buffer, ")");
+  }
+
+  @Override
+  public String toString() {
+    return Printer.repr(this);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index 2a98929..54b393a 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -454,7 +454,7 @@
     }
 
     builder.put("bazel_version", version);
-    return new ClassObject.SkylarkClassObject(builder.build(), "no native function or rule '%s'");
+    return new SkylarkClassObject(builder.build(), "no native function or rule '%s'");
   }
 
   public static ClassObject newNativeModule(RuleClassProvider ruleClassProvider, String version) {
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 2e6dbb8..cb22b50 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
@@ -62,6 +62,7 @@
 import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap;
 import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException;
 import com.google.devtools.build.lib.packages.SkylarkAspect;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 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;
@@ -70,7 +71,6 @@
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.ClassObject;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
 import com.google.devtools.build.lib.syntax.EvalException;
@@ -88,7 +88,6 @@
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.protobuf.TextFormat;
-
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -193,6 +192,22 @@
         .build();
   }
 
+  @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>"
+          + "<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"),
+      useLocation = true)
+  private static final BuiltinFunction struct = new BuiltinFunction("struct") {
+    @SuppressWarnings("unchecked")
+    public SkylarkClassObject invoke(SkylarkDict<String, Object> kwargs, Location loc)
+        throws EvalException {
+      return new SkylarkClassObject(kwargs, loc);
+    }
+  };
+
+
   // TODO(bazel-team): implement attribute copy and other rule properties
   @SkylarkSignature(name = "rule", doc =
       "Creates a new rule. Store it in a global value, so that it can be loaded and called "
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 0cc409f..52e4750 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -28,6 +28,7 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind;
 import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
@@ -36,7 +37,6 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.ClassObject;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
@@ -49,7 +49,6 @@
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.FileType;
 import com.google.devtools.build.lib.util.FileTypeSet;
-
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index 8112e02..248bbfd 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -42,12 +42,12 @@
 import com.google.devtools.build.lib.packages.OutputFile;
 import com.google.devtools.build.lib.packages.Package;
 import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.shell.ShellUtils;
 import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.FuncallExpression.FuncallException;
 import com.google.devtools.build.lib.syntax.Runtime;
@@ -58,7 +58,6 @@
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -67,7 +66,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import javax.annotation.Nullable;
 
 /** A Skylark API for the ruleContext. */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java
index 63ad021..8249fd1 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java
@@ -26,11 +26,12 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.rules.cpp.CppModuleMap;
 import com.google.devtools.build.lib.rules.cpp.LinkerInputs;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
+import com.google.devtools.build.lib.syntax.Concatable.Concatter;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -426,6 +427,11 @@
     this.strictDependencyItems = Preconditions.checkNotNull(strictDependencyItems);
   }
 
+  @Override
+  public Concatter getConcatter() {
+    return null;
+  }
+
   /**
    * All artifacts, bundleable files, etc. of the type specified by {@code key}.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProviderSkylarkConverters.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProviderSkylarkConverters.java
index 8d379e7..727891e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProviderSkylarkConverters.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProviderSkylarkConverters.java
@@ -24,8 +24,8 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.rules.objc.ObjcProvider.Key;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
index 74abb33..842e9b7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
@@ -43,11 +43,11 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
 import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
 import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.LocalMetadataCollector;
 import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
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 33b544b..4500158 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
@@ -24,16 +24,15 @@
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.AspectParameters;
 import com.google.devtools.build.lib.packages.SkylarkAspect;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.rules.SkylarkRuleContext;
 import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
 import com.google.devtools.build.lib.syntax.SkylarkType;
-
 import java.util.Map;
 
 /**
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index a31b037..131ee54 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -18,7 +18,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
+import com.google.devtools.build.lib.syntax.Concatable.Concatter;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
@@ -29,17 +29,15 @@
 import com.google.devtools.build.lib.syntax.compiler.Jump.PrimitiveComparison;
 import com.google.devtools.build.lib.syntax.compiler.LabelAdder;
 import com.google.devtools.build.lib.syntax.compiler.VariableScope;
-
-import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
-import net.bytebuddy.implementation.bytecode.Duplication;
-import net.bytebuddy.implementation.bytecode.Removal;
-import net.bytebuddy.implementation.bytecode.StackManipulation;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.IllegalFormatException;
 import java.util.List;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
 
 /**
  * Syntax node for a binary operator expression.
@@ -338,9 +336,15 @@
       return SkylarkDict.plus((SkylarkDict<?, ?>) lval, (SkylarkDict<?, ?>) rval, env);
     }
 
-    if (lval instanceof SkylarkClassObject && rval instanceof SkylarkClassObject) {
-      return SkylarkClassObject.concat(
-          (SkylarkClassObject) lval, (SkylarkClassObject) rval, location);
+    if (lval instanceof Concatable && rval instanceof Concatable) {
+      Concatable lobj = (Concatable) lval;
+      Concatable robj = (Concatable) rval;
+      Concatter concatter = lobj.getConcatter();
+      if (concatter != null && concatter.equals(robj.getConcatter())) {
+        return concatter.concat(lobj, robj, location);
+      } else {
+        throw typeException(lval, rval, Operator.PLUS, location);
+      }
     }
 
     // TODO(bazel-team): Remove this case. Union of sets should use '|' instead of '+'.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java b/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java
index 79c1961..8ea9aa1 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ClassObject.java
@@ -13,21 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.syntax;
 
-import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
-import com.google.devtools.build.lib.events.Location;
-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.util.Preconditions;
-
-import java.io.Serializable;
-import java.util.Map;
-
 import javax.annotation.Nullable;
 
 /**
@@ -53,140 +39,4 @@
    * of this struct, or returns null to use the default error message.
    */
   @Nullable String errorMessage(String name);
-
-  /** An implementation class of ClassObject for structs created in Skylark code. */
-  // TODO(bazel-team): maybe move the SkylarkModule annotation to the ClassObject interface?
-  @SkylarkModule(
-    name = "struct",
-    category = SkylarkModuleCategory.BUILTIN,
-    doc =
-        "A special language element to support structs (i.e. simple value objects). "
-            + "See the global <a href=\"globals.html#struct\">struct</a> function "
-            + "for more details."
-  )
-  public class SkylarkClassObject implements ClassObject, SkylarkValue, Serializable {
-    /** Error message to use when errorMessage argument is null. */
-    private static final String DEFAULT_ERROR_MESSAGE = "'struct' object has no attribute '%s'";
-
-    private final ImmutableMap<String, Object> values;
-    private final Location creationLoc;
-    private final String errorMessage;
-
-    /**
-     * Primarily for testing purposes where no location is available and the default
-     * errorMessage suffices.
-     */
-    public SkylarkClassObject(Map<String, Object> values) {
-      this.values = copyValues(values);
-      this.creationLoc = null;
-      this.errorMessage = DEFAULT_ERROR_MESSAGE;
-    }
-
-    /**
-     * Creates a built-in struct (i.e. without creation loc). The errorMessage has to have
-     * exactly one '%s' parameter to substitute the struct field name.
-     */
-    public SkylarkClassObject(Map<String, Object> values, String errorMessage) {
-      this.values = copyValues(values);
-      this.creationLoc = null;
-      this.errorMessage = Preconditions.checkNotNull(errorMessage);
-    }
-
-    public SkylarkClassObject(Map<String, Object> values, Location creationLoc) {
-      this.values = copyValues(values);
-      this.creationLoc = Preconditions.checkNotNull(creationLoc);
-      this.errorMessage = DEFAULT_ERROR_MESSAGE;
-    }
-
-    // Ensure that values are all acceptable to Skylark before to stuff them in a ClassObject
-    private ImmutableMap<String, Object> copyValues(Map<String, Object> values) {
-      ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
-      for (Map.Entry<String, Object> e : values.entrySet()) {
-        builder.put(e.getKey(), SkylarkType.convertToSkylark(e.getValue(), null));
-      }
-      return builder.build();
-    }
-
-    @Override
-    public Object getValue(String name) {
-      return values.get(name);
-    }
-
-    /**
-     *  Returns a value and try to cast it into specified type
-     */
-    public <TYPE> TYPE getValue(String key, Class<TYPE> type) throws EvalException {
-      Object obj = values.get(key);
-      if (obj == null) {
-        return null;
-      }
-      SkylarkType.checkType(obj, type, key);
-      return type.cast(obj);
-    }
-
-    @Override
-    public ImmutableCollection<String> getKeys() {
-      return values.keySet();
-    }
-
-    public Location getCreationLoc() {
-      return Preconditions.checkNotNull(creationLoc,
-          "This struct was not created in a Skylark code");
-    }
-
-    static SkylarkClassObject concat(
-        SkylarkClassObject lval, SkylarkClassObject rval, Location loc) throws EvalException {
-      SetView<String> commonFields = Sets.intersection(lval.values.keySet(), rval.values.keySet());
-      if (!commonFields.isEmpty()) {
-        throw new EvalException(loc, "Cannot concat structs with common field(s): "
-            + Joiner.on(",").join(commonFields));
-      }
-      return new SkylarkClassObject(ImmutableMap.<String, Object>builder()
-          .putAll(lval.values).putAll(rval.values).build(), loc);
-    }
-
-    @Override
-    public String errorMessage(String name) {
-      String suffix =
-          "Available attributes: "
-              + Joiner.on(", ").join(Ordering.natural().sortedCopy(values.keySet()));
-      return String.format(errorMessage, name) + "\n" + suffix;
-    }
-
-    @Override
-    public boolean isImmutable() {
-      for (Object item : values.values()) {
-        if (!EvalUtils.isImmutable(item)) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    /**
-     * Convert the object to string using Skylark syntax. The output tries to be
-     * reversible (but there is no guarantee, it depends on the actual values).
-     */
-    @Override
-    public void write(Appendable buffer, char quotationMark) {
-      boolean first = true;
-      Printer.append(buffer, "struct(");
-      // Sort by key to ensure deterministic output.
-      for (String key : Ordering.natural().sortedCopy(values.keySet())) {
-        if (!first) {
-          Printer.append(buffer, ", ");
-        }
-        first = false;
-        Printer.append(buffer, key);
-        Printer.append(buffer, " = ");
-        Printer.write(buffer, values.get(key), quotationMark);
-      }
-      Printer.append(buffer, ")");
-    }
-
-    @Override
-    public String toString() {
-      return Printer.repr(this);
-    }
-  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Concatable.java b/src/main/java/com/google/devtools/build/lib/syntax/Concatable.java
new file mode 100644
index 0000000..81f489f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Concatable.java
@@ -0,0 +1,36 @@
+// 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.syntax;
+
+import com.google.devtools.build.lib.events.Location;
+import javax.annotation.Nullable;
+
+/**
+ * Skylark values that support '+' operator should implement this interface.
+ */
+public interface Concatable {
+
+  /**
+   * Implements 'plus' operator on ClassObjects.
+   */
+  interface Concatter {
+    Concatable concat(Concatable lval, Concatable rval, Location loc) throws EvalException;
+  }
+
+  /* Returns a concatter for this {@link Concatable}.
+   * Two {@link Concatable}s can be added together if their concatters are equal.
+   */
+  @Nullable
+  Concatter getConcatter();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index 4a66189..386a9ae 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -26,12 +26,10 @@
 import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
-import net.bytebuddy.implementation.bytecode.StackManipulation;
-
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
 
 /**
  * Utilities used by the evaluator.
@@ -280,8 +278,6 @@
     } else if (NestedSet.class.isAssignableFrom(c) || SkylarkNestedSet.class.isAssignableFrom(c)) {
       // TODO(bazel-team): no one should be seeing naked NestedSet at all.
       return "set";
-    } else if (ClassObject.SkylarkClassObject.class.isAssignableFrom(c)) {
-      return "struct";
     } else {
       if (c.getSimpleName().isEmpty()) {
         return c.getName();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index f103211..4b39a7d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -29,11 +29,9 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.syntax.Type.ConversionException;
-
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -1850,21 +1848,6 @@
         }
       };
 
-  @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>"
-      + "<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"),
-      useLocation = true)
-  private static final BuiltinFunction struct = new BuiltinFunction("struct") {
-    @SuppressWarnings("unchecked")
-    public SkylarkClassObject invoke(SkylarkDict<String, Object> kwargs, Location loc)
-        throws EvalException {
-      return new SkylarkClassObject(kwargs, loc);
-    }
-  };
-
   @SkylarkSignature(
       name = "set",
       returnType = SkylarkNestedSet.class,
@@ -2345,7 +2328,7 @@
   static final List<BaseFunction> skylarkGlobalFunctions =
       ImmutableList.<BaseFunction>builder()
           .addAll(buildGlobalFunctions)
-          .add(dir, fail, getattr, hasattr, hash, print, struct, type)
+          .add(dir, fail, getattr, hasattr, hash, print, type)
           .build();
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java
index 0f8a3d3..2a64d79 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java
@@ -24,13 +24,11 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 import com.google.devtools.build.lib.util.Preconditions;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-
 import javax.annotation.Nullable;
 
 /** A generic type safe NestedSet wrapper for Skylark. */
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
index f662ea1..e73672a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
@@ -27,7 +27,6 @@
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.util.Preconditions;
-
 import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
@@ -37,7 +36,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-
 import javax.annotation.Nullable;
 
 /**
@@ -168,9 +166,6 @@
   /** The BOOLEAN type, that contains TRUE and FALSE */
   public static final Simple BOOL = Simple.of(Boolean.class);
 
-  /** The STRUCT type, for all Struct's */
-  public static final Simple STRUCT = Simple.of(ClassObject.SkylarkClassObject.class);
-
   /** The FUNCTION type, that contains all functions, otherwise dynamically typed at call-time */
   public static final SkylarkFunctionType FUNCTION = new SkylarkFunctionType("unknown", TOP);
 
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 65d0c48ab..07a8c6b 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
@@ -22,6 +22,7 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -33,15 +34,23 @@
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
 import com.google.devtools.build.lib.packages.SkylarkAspect;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.rules.SkylarkAttr;
 import com.google.devtools.build.lib.rules.SkylarkFileType;
 import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions;
 import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions.RuleFunction;
 import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+import com.google.devtools.build.lib.syntax.ClassObject;
+import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.SkylarkDict;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.FileTypeSet;
-
+import java.util.Collection;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -748,4 +757,213 @@
     // Should succeed without a "licenses attribute is potentially configurable" loading error:
     createRuleContext("//third_party/foo:r");
   }
+
+  @Test
+  public void testStructCreation() throws Exception {
+    // TODO(fwe): cannot be handled by current testing suite
+    eval("x = struct(a = 1, b = 2)");
+    assertThat(lookup("x")).isInstanceOf(ClassObject.class);
+  }
+
+  @Test
+  public void testStructFields() throws Exception {
+    // TODO(fwe): cannot be handled by current testing suite
+    eval("x = struct(a = 1, b = 2)");
+    ClassObject x = (ClassObject) lookup("x");
+    assertEquals(1, x.getValue("a"));
+    assertEquals(2, x.getValue("b"));
+  }
+
+  @Test
+  public void testStructAccessingFieldsFromSkylark() throws Exception {
+    eval("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b");
+    assertThat(lookup("x1")).isEqualTo(1);
+    assertThat(lookup("x2")).isEqualTo(2);
+  }
+
+  @Test
+  public void testStructAccessingUnknownField() throws Exception {
+    checkErrorContains(
+            "'struct' object has no attribute 'c'\n" + "Available attributes: a, b",
+            "x = struct(a = 1, b = 2)",
+            "y = x.c");
+  }
+
+  @Test
+  public void testStructAccessingUnknownFieldWithArgs() throws Exception {
+    checkErrorContains(
+        "struct has no method 'c'", "x = struct(a = 1, b = 2)", "y = x.c()");
+  }
+
+  @Test
+  public void testStructAccessingNonFunctionFieldWithArgs() throws Exception {
+    checkErrorContains(
+        "struct field 'a' is not a function", "x = struct(a = 1, b = 2)", "x1 = x.a(1)");
+  }
+
+  @Test
+  public void testStructAccessingFunctionFieldWithArgs() throws Exception {
+    eval("def f(x): return x+5", "x = struct(a = f, b = 2)", "x1 = x.a(1)");
+    assertThat(lookup("x1")).isEqualTo(6);
+  }
+
+  @Test
+  public void testStructPosArgs() throws Exception {
+    checkErrorContains(
+        "struct(**kwargs) does not accept positional arguments, but got 1", "x = struct(1, b = 2)");
+  }
+
+  @Test
+  public void testStructConcatenationFieldNames() throws Exception {
+    // TODO(fwe): cannot be handled by current testing suite
+    eval("x = struct(a = 1, b = 2)",
+        "y = struct(c = 1, d = 2)",
+        "z = x + y\n");
+    SkylarkClassObject z = (SkylarkClassObject) lookup("z");
+    assertEquals(ImmutableSet.of("a", "b", "c", "d"), z.getKeys());
+  }
+
+  @Test
+  public void testStructConcatenationFieldValues() throws Exception {
+    // TODO(fwe): cannot be handled by current testing suite
+    eval("x = struct(a = 1, b = 2)",
+        "y = struct(c = 1, d = 2)",
+        "z = x + y\n");
+    SkylarkClassObject z = (SkylarkClassObject) lookup("z");
+    assertEquals(1, z.getValue("a"));
+    assertEquals(2, z.getValue("b"));
+    assertEquals(1, z.getValue("c"));
+    assertEquals(2, z.getValue("d"));
+  }
+
+  @Test
+  public void testStructConcatenationCommonFields() throws Exception {
+    checkErrorContains("Cannot concat structs with common field(s): a",
+        "x = struct(a = 1, b = 2)", "y = struct(c = 1, a = 2)", "z = x + y\n");
+  }
+
+  @Test
+  public void testConditionalStructConcatenation() throws Exception {
+    // TODO(fwe): cannot be handled by current testing suite
+    eval("def func():",
+        "  x = struct(a = 1, b = 2)",
+        "  if True:",
+        "    x += struct(c = 1, d = 2)",
+        "  return x",
+        "x = func()");
+    SkylarkClassObject x = (SkylarkClassObject) lookup("x");
+    assertEquals(1, x.getValue("a"));
+    assertEquals(2, x.getValue("b"));
+    assertEquals(1, x.getValue("c"));
+    assertEquals(2, x.getValue("d"));
+  }
+
+  @Test
+  public void testGetattrNoAttr() throws Exception {
+    checkErrorContains("Object of type 'struct' has no attribute \"b\"",
+        "s = struct(a='val')", "getattr(s, 'b')");
+  }
+
+  @Test
+  public void testGetattr() throws Exception {
+    eval(
+        "s = struct(a='val')",
+        "x = getattr(s, 'a')",
+        "y = getattr(s, 'b', 'def')",
+        "z = getattr(s, 'b', default = 'def')",
+        "w = getattr(s, 'a', default='ignored')");
+    assertThat(lookup("x")).isEqualTo("val");
+    assertThat(lookup("y")).isEqualTo("def");
+    assertThat(lookup("z")).isEqualTo("def");
+    assertThat(lookup("w")).isEqualTo("val");
+  }
+
+  @Test
+  public void testHasattr() throws Exception {
+    eval("s = struct(a=1)",
+        "x = hasattr(s, 'a')",
+        "y = hasattr(s, 'b')\n");
+    assertThat(lookup("x")).isEqualTo(true);
+    assertThat(lookup("y")).isEqualTo(false);
+  }
+
+  @Test
+  public void testStructStr() throws Exception {
+    assertThat(eval("str(struct(x = 2, y = 3, z = 4))"))
+        .isEqualTo("struct(x = 2, y = 3, z = 4)");
+  }
+
+  @Test
+  public void testStructsInSets() throws Exception {
+    eval("set([struct(a='a')])");
+  }
+
+  @Test
+  public void testStructMembersAreImmutable() throws Exception {
+    checkErrorContains(
+        "can only assign to variables and tuples, not to 's.x'",
+        "s = struct(x = 'a')",
+        "s.x = 'b'\n");
+  }
+
+  @Test
+  public void testStructDictMembersAreImmutable() throws Exception {
+    checkErrorContains(
+        "can only assign to variables and tuples, not to 's.x['b']'",
+        "s = struct(x = {'a' : 1})",
+        "s.x['b'] = 2\n");
+  }
+
+  @Test
+  public void testNsetGoodCompositeItem() throws Exception {
+    eval("def func():",
+        "  return set([struct(a='a')])",
+        "s = func()");
+    Collection<Object> result = ((SkylarkNestedSet) lookup("s")).toCollection();
+    assertThat(result).hasSize(1);
+    assertThat(result.iterator().next()).isInstanceOf(SkylarkClassObject.class);
+  }
+
+  @Test
+  public void testNsetBadMutableItem() throws Exception {
+    checkEvalError("sets cannot contain mutable items", "set([([],)])");
+    checkEvalError("sets cannot contain mutable items", "set([struct(a=[])])");
+  }
+
+  private static SkylarkClassObject makeStruct(String field, Object value) {
+    return new SkylarkClassObject(ImmutableMap.of(field, value));
+  }
+
+  private static SkylarkClassObject makeBigStruct(Environment env) {
+    // struct(a=[struct(x={1:1}), ()], b=(), c={2:2})
+    return new SkylarkClassObject(ImmutableMap.<String, Object>of(
+        "a", MutableList.<Object>of(env,
+            new SkylarkClassObject(ImmutableMap.<String, Object>of(
+                "x", SkylarkDict.<Object, Object>of(env, 1, 1))),
+            Tuple.of()),
+        "b", Tuple.of(),
+        "c", SkylarkDict.<Object, Object>of(env, 2, 2)));
+  }
+
+  @Test
+  public void testStructMutabilityShallow() throws Exception {
+    assertTrue(EvalUtils.isImmutable(makeStruct("a", 1)));
+  }
+
+  private static MutableList<Object> makeList(Environment env) {
+    return MutableList.<Object>of(env, 1, 2, 3);
+  }
+
+
+  @Test
+  public void testStructMutabilityDeep() throws Exception {
+    assertTrue(EvalUtils.isImmutable(Tuple.<Object>of(makeList(null))));
+    assertTrue(EvalUtils.isImmutable(makeStruct("a", makeList(null))));
+    assertTrue(EvalUtils.isImmutable(makeBigStruct(null)));
+
+    assertFalse(EvalUtils.isImmutable(Tuple.<Object>of(makeList(ev.getEnvironment()))));
+    assertFalse(EvalUtils.isImmutable(makeStruct("a", makeList(ev.getEnvironment()))));
+    assertFalse(EvalUtils.isImmutable(makeBigStruct(ev.getEnvironment())));
+  }
+
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
index cf17c9d..5efd8a8 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
@@ -149,6 +149,7 @@
   }
 
   protected void checkErrorContains(String errorMsg, String... lines) throws Exception {
+    ev.setFailFast(false);
     try {
       eval(lines);
       fail("checkErrorContains(String, String...): There was no error");
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java
index 26763ea..7ce40aa 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java
@@ -19,8 +19,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
@@ -44,21 +42,6 @@
     return SkylarkDict.<Object, Object>of(env, 1, 1, 2, 2);
   }
 
-  private static SkylarkClassObject makeStruct(String field, Object value) {
-    return new SkylarkClassObject(ImmutableMap.of(field, value));
-  }
-
-  private static SkylarkClassObject makeBigStruct(Environment env) {
-    // struct(a=[struct(x={1:1}), ()], b=(), c={2:2})
-    return new SkylarkClassObject(ImmutableMap.<String, Object>of(
-        "a", MutableList.<Object>of(env,
-            new SkylarkClassObject(ImmutableMap.<String, Object>of(
-                "x", SkylarkDict.<Object, Object>of(env, 1, 1))),
-            Tuple.of()),
-        "b", Tuple.of(),
-        "c", SkylarkDict.<Object, Object>of(env, 2, 2)));
-  }
-
   @Test
   public void testEmptyStringToIterable() throws Exception {
     assertThat(EvalUtils.toIterable("", null)).isEmpty();
@@ -88,7 +71,6 @@
   @Test
   public void testDatatypeMutabilityShallow() throws Exception {
     assertTrue(EvalUtils.isImmutable(Tuple.of(1, 2, 3)));
-    assertTrue(EvalUtils.isImmutable(makeStruct("a", 1)));
 
     // Mutability depends on the environment.
     assertTrue(EvalUtils.isImmutable(makeList(null)));
@@ -100,12 +82,8 @@
   @Test
   public void testDatatypeMutabilityDeep() throws Exception {
     assertTrue(EvalUtils.isImmutable(Tuple.<Object>of(makeList(null))));
-    assertTrue(EvalUtils.isImmutable(makeStruct("a", makeList(null))));
-    assertTrue(EvalUtils.isImmutable(makeBigStruct(null)));
 
     assertFalse(EvalUtils.isImmutable(Tuple.<Object>of(makeList(env))));
-    assertFalse(EvalUtils.isImmutable(makeStruct("a", makeList(env))));
-    assertFalse(EvalUtils.isImmutable(makeBigStruct(env)));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index 1eaecb7..5f994db 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -22,7 +22,6 @@
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -1661,9 +1660,6 @@
         .testStatement("str(False)", "False")
         .testStatement("str(None)", "None")
         .testStatement("str(str)", "<function str>");
-
-    new SkylarkTest()
-        .testStatement("str(struct(x = 2, y = 3, z = 4))", "struct(x = 2, y = 3, z = 4)");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index 2b6ebfc..235968f 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
 import com.google.devtools.build.lib.analysis.FileConfiguredTarget;
@@ -30,7 +29,6 @@
 import com.google.devtools.build.lib.skylarkinterface.Param;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.testutil.TestMode;
 import org.junit.Before;
@@ -712,22 +710,6 @@
   }
 
   @Test
-  public void testConditionalStructConcatenation() throws Exception {
-    // TODO(fwe): cannot be handled by current testing suite
-    eval("def func():",
-        "  x = struct(a = 1, b = 2)",
-        "  if True:",
-        "    x += struct(c = 1, d = 2)",
-        "  return x",
-        "x = func()");
-    SkylarkClassObject x = (SkylarkClassObject) lookup("x");
-    assertEquals(1, x.getValue("a"));
-    assertEquals(2, x.getValue("b"));
-    assertEquals(1, x.getValue("c"));
-    assertEquals(2, x.getValue("d"));
-  }
-
-  @Test
   public void testJavaFunctionReturnsMutableObject() throws Exception {
     new SkylarkTest()
         .update("mock", new Mock())
@@ -825,93 +807,6 @@
   }
 
   @Test
-  public void testStructCreation() throws Exception {
-    // TODO(fwe): cannot be handled by current testing suite
-    eval("x = struct(a = 1, b = 2)");
-    assertThat(lookup("x")).isInstanceOf(ClassObject.class);
-  }
-
-  @Test
-  public void testStructFields() throws Exception {
-    // TODO(fwe): cannot be handled by current testing suite
-    eval("x = struct(a = 1, b = 2)");
-    ClassObject x = (ClassObject) lookup("x");
-    assertEquals(1, x.getValue("a"));
-    assertEquals(2, x.getValue("b"));
-  }
-
-  @Test
-  public void testStructAccessingFieldsFromSkylark() throws Exception {
-    new SkylarkTest()
-        .setUp("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b")
-        .testLookup("x1", 1)
-        .testLookup("x2", 2);
-  }
-
-  @Test
-  public void testStructAccessingUnknownField() throws Exception {
-    new SkylarkTest()
-        .testIfErrorContains(
-            "'struct' object has no attribute 'c'\n" + "Available attributes: a, b",
-            "x = struct(a = 1, b = 2)",
-            "y = x.c");
-  }
-
-  @Test
-  public void testStructAccessingUnknownFieldWithArgs() throws Exception {
-    new SkylarkTest().testIfExactError(
-        "struct has no method 'c'", "x = struct(a = 1, b = 2)", "y = x.c()");
-  }
-
-  @Test
-  public void testStructAccessingNonFunctionFieldWithArgs() throws Exception {
-    new SkylarkTest().testIfExactError(
-        "struct field 'a' is not a function", "x = struct(a = 1, b = 2)", "x1 = x.a(1)");
-  }
-
-  @Test
-  public void testStructAccessingFunctionFieldWithArgs() throws Exception {
-    new SkylarkTest()
-        .setUp("def f(x): return x+5", "x = struct(a = f, b = 2)", "x1 = x.a(1)")
-        .testLookup("x1", 6);
-  }
-
-  @Test
-  public void testStructPosArgs() throws Exception {
-    new SkylarkTest().testIfExactError(
-        "struct(**kwargs) does not accept positional arguments, but got 1", "x = struct(1, b = 2)");
-  }
-
-  @Test
-  public void testStructConcatenationFieldNames() throws Exception {
-    // TODO(fwe): cannot be handled by current testing suite
-    eval("x = struct(a = 1, b = 2)",
-        "y = struct(c = 1, d = 2)",
-        "z = x + y\n");
-    SkylarkClassObject z = (SkylarkClassObject) lookup("z");
-    assertEquals(ImmutableSet.of("a", "b", "c", "d"), z.getKeys());
-  }
-
-  @Test
-  public void testStructConcatenationFieldValues() throws Exception {
-    // TODO(fwe): cannot be handled by current testing suite
-    eval("x = struct(a = 1, b = 2)",
-        "y = struct(c = 1, d = 2)",
-        "z = x + y\n");
-    SkylarkClassObject z = (SkylarkClassObject) lookup("z");
-    assertEquals(1, z.getValue("a"));
-    assertEquals(2, z.getValue("b"));
-    assertEquals(1, z.getValue("c"));
-    assertEquals(2, z.getValue("d"));
-  }
-
-  @Test
-  public void testStructConcatenationCommonFields() throws Exception {
-    new SkylarkTest().testIfExactError("Cannot concat structs with common field(s): a",
-        "x = struct(a = 1, b = 2)", "y = struct(c = 1, a = 2)", "z = x + y\n");
-  }
-
-  @Test
   public void testDotExpressionOnNonStructObject() throws Exception {
     new SkylarkTest().testIfExactError("Object of type 'string' has no field \"field\"",
         "x = 'a'.field");
@@ -1026,15 +921,6 @@
   }
 
   @Test
-  public void testHasattr() throws Exception {
-    new SkylarkTest().setUp("s = struct(a=1)",
-      "x = hasattr(s, 'a')",
-      "y = hasattr(s, 'b')\n")
-      .testLookup("x", Boolean.TRUE)
-      .testLookup("y", Boolean.FALSE);
-  }
-
-  @Test
   public void testHasattrMethods() throws Exception {
     new SkylarkTest()
         .update("mock", new Mock())
@@ -1049,23 +935,6 @@
   }
 
   @Test
-  public void testGetattr() throws Exception {
-    new SkylarkTest()
-        .setUp("s = struct(a='val')", "x = getattr(s, 'a')", "y = getattr(s, 'b', 'def')",
-            "z = getattr(s, 'b', default = 'def')", "w = getattr(s, 'a', default='ignored')")
-        .testLookup("x", "val")
-        .testLookup("y", "def")
-        .testLookup("z", "def")
-        .testLookup("w", "val");
-  }
-
-  @Test
-  public void testGetattrNoAttr() throws Exception {
-    new SkylarkTest().testIfExactError("Object of type 'struct' has no attribute \"b\"",
-        "s = struct(a='val')", "getattr(s, 'b')");
-  }
-
-  @Test
   public void testListAnTupleConcatenationDoesNotWorkInSkylark() throws Exception {
     new SkylarkTest().testIfExactError("unsupported operand type(s) for +: 'list' and 'tuple'",
         "[1, 2] + (3, 4)");
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
index 5c6535e..a0d8420 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
@@ -18,19 +18,15 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.collect.nestedset.Order;
-import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 /**
  * Tests for SkylarkNestedSet.
@@ -156,22 +152,6 @@
   }
 
   @Test
-  public void testNsetGoodCompositeItem() throws Exception {
-    eval("def func():",
-         "  return set([struct(a='a')])",
-         "s = func()");
-    Collection<Object> result = get("s").toCollection();
-    assertThat(result).hasSize(1);
-    assertThat(result.iterator().next()).isInstanceOf(SkylarkClassObject.class);
-  }
-
-  @Test
-  public void testNsetBadMutableItem() throws Exception {
-    checkEvalError("sets cannot contain mutable items", "set([([],)])");
-    checkEvalError("sets cannot contain mutable items", "set([struct(a=[])])");
-  }
-
-  @Test
   public void testNsetToString() throws Exception {
     eval("s = set() + [2, 4, 6] + [3, 4, 5]",
         "x = str(s)");
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
index c44e5c9..b7bd6f6 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
@@ -18,11 +18,11 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget;
 import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -112,22 +112,6 @@
   }
 
   @Test
-  public void testStructMembersAreImmutable() {
-    checkError(
-        "can only assign to variables and tuples, not to 's.x'",
-        "s = struct(x = 'a')",
-        "s.x = 'b'\n");
-  }
-
-  @Test
-  public void testStructDictMembersAreImmutable() {
-    checkError(
-        "can only assign to variables and tuples, not to 's.x['b']'",
-        "s = struct(x = {'a' : 1})",
-        "s.x['b'] = 2\n");
-  }
-
-  @Test
   public void testTupleLiteralWorksForDifferentTypes() throws Exception {
     parse("('a', 1)");
   }
@@ -299,11 +283,11 @@
 
     // TODO(bazel-team): fix that?
     assertThat(ClassObject.class.isAnnotationPresent(SkylarkModule.class)).isFalse();
-    assertThat(ClassObject.SkylarkClassObject.class.isAnnotationPresent(SkylarkModule.class))
+    assertThat(SkylarkClassObject.class.isAnnotationPresent(SkylarkModule.class))
         .isTrue();
     assertThat(
-            EvalUtils.getParentWithSkylarkModule(ClassObject.SkylarkClassObject.class)
-                == ClassObject.SkylarkClassObject.class)
+            EvalUtils.getParentWithSkylarkModule(SkylarkClassObject.class)
+                == SkylarkClassObject.class)
         .isTrue();
     assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.class)).isNull();
   }
@@ -320,8 +304,6 @@
     assertThat(SkylarkType.of(tupleClass)).isEqualTo(SkylarkType.TUPLE);
     assertThat(SkylarkType.TUPLE).isNotEqualTo(SkylarkType.LIST);
 
-    // Also for ClassObject
-    assertThat(SkylarkType.of(ClassObject.SkylarkClassObject.class)).isEqualTo(SkylarkType.STRUCT);
     try {
       SkylarkType.of(ClassObject.class);
       throw new Exception("foo");
@@ -335,7 +317,7 @@
     // TODO(bazel-team): move to some other place to remove dependency of syntax tests on Artifact?
     assertThat(SkylarkType.of(Artifact.SpecialArtifact.class))
         .isEqualTo(SkylarkType.of(Artifact.class));
-    assertThat(SkylarkType.of(RuleConfiguredTarget.class)).isNotEqualTo(SkylarkType.STRUCT);
+    assertThat(SkylarkType.of(RuleConfiguredTarget.class)).isNotEqualTo(SkylarkType.of(SkylarkClassObject.class));
   }
 
   @Test
@@ -348,9 +330,8 @@
     assertThat(SkylarkType.LIST.includes(combo1)).isTrue();
 
     SkylarkType union1 =
-        SkylarkType.Union.of(SkylarkType.DICT, SkylarkType.LIST, SkylarkType.STRUCT);
+        SkylarkType.Union.of(SkylarkType.DICT, SkylarkType.LIST);
     assertThat(union1.includes(SkylarkType.DICT)).isTrue();
-    assertThat(union1.includes(SkylarkType.STRUCT)).isTrue();
     assertThat(union1.includes(combo1)).isTrue();
     assertThat(union1.includes(SkylarkType.STRING)).isFalse();