// Copyright 2014 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.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.collect.nestedset.NestedSet; 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.events.Location; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; 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. */ @SkylarkModule(name = "set", doc = "A language built-in type that supports sets. " + "Sets can be created using the set function, and " + "they support the | operator to extend the set with more elements or " + "to nest other sets inside of it. Examples:
" + "
s = set([1, 2])\n"
        + "s = s | [3]           # s == {1, 2, 3}\n"
        + "s = s | set([4, 5])   # s == {1, 2, 3, {4, 5}}\n"
        + "other = set([\"a\", \"b\", \"c\"], order=\"compile\")
" + "Note that in these examples {..} is not a valid literal to create sets. " + "Sets have a fixed generic type, so set([1]) + [\"a\"] or " + "set([1]) + set([\"a\"]) results in an error.
" + "Elements in a set can neither be mutable or be of type list, " + "struct or dict.
" + "When aggregating data from providers, sets can take significantly less memory than " + "other types as they support nesting, that is, their subsets are shared in memory.
" + "Every set has an order parameter which determines the iteration order. " + "There are four possible values:" + "" + "Except for stable, the above values are incompatible with each other. " + "Consequently, two sets can only be merged via the | operator or via " + "union() if either both sets have the same order or one of " + "the sets has stable order. In the latter case the iteration order will be " + "determined by the outer set, thus ignoring the order parameter of " + "nested sets.") @Immutable public final class SkylarkNestedSet implements Iterable, SkylarkValue { private final SkylarkType contentType; @Nullable private final List items; @Nullable private final List> transitiveItems; private final NestedSet set; public SkylarkNestedSet(Order order, Object item, Location loc) throws EvalException { this(order, SkylarkType.TOP, item, loc, new ArrayList(), new ArrayList>()); } public SkylarkNestedSet(SkylarkNestedSet left, Object right, Location loc) throws EvalException { this(left.set.getOrder(), left.contentType, right, loc, new ArrayList(checkItems(left.items, loc)), new ArrayList>(checkItems(left.transitiveItems, loc))); } private static T checkItems(T items, Location loc) throws EvalException { // SkylarkNestedSets created directly from ordinary NestedSets (those were created in a // native rule) don't have directly accessible items and transitiveItems, so we cannot // add more elements to them. if (items == null) { throw new EvalException(loc, "Cannot add more elements to this set. Sets created in " + "native rules cannot be left side operands of the + operator."); } return items; } // This is safe because of the type checking @SuppressWarnings("unchecked") private SkylarkNestedSet(Order order, SkylarkType contentType, Object item, Location loc, List items, List> transitiveItems) throws EvalException { // Adding the item if (item instanceof SkylarkNestedSet) { SkylarkNestedSet nestedSet = (SkylarkNestedSet) item; if (!nestedSet.isEmpty()) { contentType = checkType(contentType, nestedSet.contentType, loc); transitiveItems.add((NestedSet) nestedSet.set); } } else if (item instanceof SkylarkList) { // TODO(bazel-team): we should check ImmutableList here but it screws up genrule at line 43 for (Object object : (SkylarkList) item) { contentType = checkType(contentType, SkylarkType.of(object.getClass()), loc); items.add(object); } } else { throw new EvalException( loc, String.format("cannot add value of type '%s' to a set", EvalUtils.getDataTypeName(item))); } this.contentType = Preconditions.checkNotNull(contentType, "type cannot be null"); // Initializing the real nested set NestedSetBuilder builder = new NestedSetBuilder<>(order); builder.addAll(items); try { for (NestedSet nestedSet : transitiveItems) { builder.addTransitive(nestedSet); } } catch (IllegalStateException e) { throw new EvalException(loc, e.getMessage()); } this.set = builder.build(); this.items = ImmutableList.copyOf(items); this.transitiveItems = ImmutableList.copyOf(transitiveItems); } /** * Returns a type safe SkylarkNestedSet. Use this instead of the constructor if possible. */ public static SkylarkNestedSet of(SkylarkType contentType, NestedSet set) { return new SkylarkNestedSet(contentType, set); } /** * Returns a type safe SkylarkNestedSet. Use this instead of the constructor if possible. */ public static SkylarkNestedSet of(Class contentType, NestedSet set) { return of(SkylarkType.of(contentType), set); } /** * A not type safe constructor for SkylarkNestedSet. It's discouraged to use it unless type * generic safety is guaranteed from the caller side. */ SkylarkNestedSet(SkylarkType contentType, NestedSet set) { // This is here for the sake of FuncallExpression. this.contentType = Preconditions.checkNotNull(contentType, "type cannot be null"); this.set = Preconditions.checkNotNull(set, "set cannot be null"); this.items = null; this.transitiveItems = null; } /** * A not type safe constructor for SkylarkNestedSet, specifying type as a Java class. * It's discouraged to use it unless type generic safety is guaranteed from the caller side. */ public SkylarkNestedSet(Class contentType, NestedSet set) { this(SkylarkType.of(contentType), set); } private static SkylarkType checkType(SkylarkType builderType, SkylarkType itemType, Location loc) throws EvalException { if (SkylarkType.intersection( SkylarkType.Union.of(SkylarkType.DICT, SkylarkType.LIST, SkylarkType.STRUCT), itemType) != SkylarkType.BOTTOM) { throw new EvalException( loc, String.format("sets cannot contain items of type '%s'", itemType)); } if (!EvalUtils.isImmutable(itemType.getType())) { throw new EvalException( loc, String.format("sets cannot contain items of type '%s' (mutable type)", itemType)); } SkylarkType newType = SkylarkType.intersection(builderType, itemType); if (newType == SkylarkType.BOTTOM) { throw new EvalException( loc, String.format("cannot add an item of type '%s' to a set of '%s'", itemType, builderType)); } return newType; } /** * Returns the NestedSet embedded in this SkylarkNestedSet if it is of the parameter type. */ // The precondition ensures generic type safety @SuppressWarnings("unchecked") public NestedSet getSet(Class type) { // Empty sets don't need have to have a type since they don't have items if (set.isEmpty()) { return (NestedSet) set; } Preconditions.checkArgument(contentType.canBeCastTo(type), String.format("Expected a set of '%s' but got a set of '%s'", EvalUtils.getDataTypeNameFromClass(type), contentType)); return (NestedSet) set; } public Set expandedSet() { return set.toSet(); } // For some reason this cast is unsafe in Java @SuppressWarnings("unchecked") @Override public Iterator iterator() { return (Iterator) set.iterator(); } public Collection toCollection() { // Do not remove : workaround for Java 7 type inference. return ImmutableList.copyOf(set.toCollection()); } public boolean isEmpty() { return set.isEmpty(); } @VisibleForTesting public SkylarkType getContentType() { return contentType; } @Override public String toString() { return Printer.repr(this); } public Order getOrder() { return set.getOrder(); } @Override public boolean isImmutable() { return true; } @Override public void write(Appendable buffer, char quotationMark) { Printer.append(buffer, "set("); Printer.printList(buffer, this, "[", ", ", "]", null, quotationMark); Order order = getOrder(); if (order != Order.STABLE_ORDER) { Printer.append(buffer, ", order = \"" + order.getName() + "\""); } Printer.append(buffer, ")"); } }