// 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.base.Joiner;
import com.google.common.base.Preconditions;
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;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.util.LoggingUtil;
import com.google.devtools.build.lib.util.StringCanonicalizer;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import javax.annotation.Nullable;
/**
*
Root of Type symbol hierarchy for values in the build language.
*
*
Type symbols are primarily used for their convert method,
* which is a kind of cast operator enabling conversion from untyped (Object)
* references to values in the build language, to typed references.
*
*
For example, this code type-converts a value x returned by
* the evaluator, to a list of strings:
*
*
* Object x = expr.eval(env);
* List<String> s = Type.STRING_LIST.convert(x);
*
*/
public abstract class Type {
protected Type() {}
/**
* Converts untyped Object x resulting from the evaluation of an expression in the build language,
* into a typed object of type T.
*
*
x must be *directly* convertible to this type. This therefore disqualifies "selector
* expressions" of the form "{ config1: 'value1_of_orig_type', config2: 'value2_of_orig_type; }"
* (which support configurable attributes). To handle those expressions, see
* {@link com.google.devtools.build.lib.packages.BuildType#selectableConvert}.
*
* @param x the build-interpreter value to convert.
* @param what an object having a toString describing what x is for; should be included in
* any exception thrown. Grammatically, must produce a string describe a syntactic
* construct, e.g. "attribute 'srcs' of rule foo".
* @param context the label of the current BUILD rule; must be non-null if resolution of
* package-relative label strings is required
* @throws ConversionException if there was a problem performing the type conversion
*/
public abstract T convert(Object x, Object what, @Nullable Object context)
throws ConversionException;
// TODO(bazel-team): Check external calls (e.g. in PackageFactory), verify they always want
// this over selectableConvert.
/**
* Equivalent to {@link #convert(Object, String, Object)} where the label is {@code null}.
* Useful for converting values to types that do not involve the type {@code LABEL}
* and hence do not require the label of the current package.
*/
public final T convert(Object x, Object what) throws ConversionException {
return convert(x, what, null);
}
/**
* Like {@link #convert(Object, String, Object)}, but converts skylark {@code None}
* to given {@code defaultValue}.
*/
@Nullable public final T convertOptional(Object x,
String what, @Nullable Object context, T defaultValue)
throws ConversionException {
if (EvalUtils.isNullOrNone(x)) {
return defaultValue;
}
return convert(x, what, context);
}
/**
* Like {@link #convert(Object, String, Object)}, but converts skylark {@code None}
* to java {@code null}.
*/
@Nullable public final T convertOptional(Object x, String what, @Nullable Object context)
throws ConversionException {
return convertOptional(x, what, context, null);
}
/**
* Like {@link #convert(Object, String)}, but converts skylark {@code NONE} to java {@code null}.
*/
@Nullable public final T convertOptional(Object x, String what) throws ConversionException {
return convertOptional(x, what, null);
}
public abstract T cast(Object value);
@Override
public abstract String toString();
/**
* Returns the default value for this type; may return null iff no default is defined for this
* type.
*/
public abstract T getDefaultValue();
/** Function accepting a (potentially null) object value. See {@link #visitLabels}. */
public static interface LabelVisitor {
void visit(@Nullable Label label) throws InterruptedException;
}
/**
* Extracts all labels associated with the instance of the type to visitor.
*
*
This is used to support reliable label visitation in
* {@link com.google.devtools.build.lib.packages.AbstractAttributeMapper#visitLabels}. To preserve
* that reliability, every type should faithfully define its own instance of this method. In other
* words, be careful about defining default instances in base types that get auto-inherited by
* their children. Keep all definitions as explicit as possible.
*/
public abstract void visitLabels(LabelVisitor visitor, Object value) throws InterruptedException;
/** Classifications of labels by their usage. */
public enum LabelClass {
/** Used for types which are not labels. */
NONE,
/** Used for types which use labels to declare a dependency. */
DEPENDENCY,
/**
* Used for types which use labels to reference another target but do not declare a dependency,
* in cases where doing so would cause a dependency cycle.
*/
NONDEP_REFERENCE,
/** Used for types which use labels to declare an output path. */
OUTPUT,
/**
* Used for types which contain Fileset entries, which contain labels but do not produce
* normal dependencies.
*/
FILESET_ENTRY
}
/** Returns the class of labels contained by this type, if any. */
public LabelClass getLabelClass() {
return LabelClass.NONE;
}
/**
* Implementation of concatenation for this type (e.g. "val1 + val2"). Returns null to
* indicate concatenation isn't supported.
*/
public T concat(@SuppressWarnings("unused") Iterable elements) {
return null;
}
/**
* Converts an initialized Type object into a tag set representation.
* This operation is only valid for certain sub-Types which are guaranteed
* to be properly initialized.
*
* @param value the actual value
* @throws UnsupportedOperationException if the concrete type does not support
* tag conversion or if a convertible type has no initialized value.
*/
public Set toTagSet(Object value, String name) {
String msg = "Attribute " + name + " does not support tag conversion.";
throw new UnsupportedOperationException(msg);
}
/**
* The type of an integer.
*/
public static final Type INTEGER = new IntegerType();
/**
* The type of a string.
*/
public static final Type STRING = new StringType();
/**
* The type of a boolean.
*/
public static final Type BOOLEAN = new BooleanType();
/**
* The type of a list of not-yet-typed objects.
*/
public static final ObjectListType OBJECT_LIST = new ObjectListType();
/**
* The type of a list of {@linkplain #STRING strings}.
*/
public static final ListType STRING_LIST = ListType.create(STRING);
/**
* The type of a list of {@linkplain #INTEGER strings}.
*/
public static final ListType INTEGER_LIST = ListType.create(INTEGER);
/**
* The type of a dictionary of {@linkplain #STRING strings}.
*/
public static final DictType STRING_DICT = DictType.create(STRING, STRING);
/**
* The type of a dictionary of {@linkplain #STRING_LIST label lists}.
*/
public static final DictType> STRING_LIST_DICT =
DictType.create(STRING, STRING_LIST);
/**
* The type of a dictionary of {@linkplain #STRING strings}, where each entry
* maps to a single string value.
*/
public static final DictType STRING_DICT_UNARY = DictType.create(STRING, STRING);
/**
* For ListType objects, returns the type of the elements of the list; for
* all other types, returns null. (This non-obvious implementation strategy
* is necessitated by the wildcard capture rules of the Java type system,
* which disallow conversion from Type{List{ELEM}} to Type{List{?}}.)
*/
public Type> getListElementType() {
return null;
}
/**
* ConversionException is thrown when a type-conversion fails; it contains
* an explanatory error message.
*/
public static class ConversionException extends EvalException {
private static String message(Type> type, Object value, @Nullable Object what) {
StringBuilder builder = new StringBuilder();
builder.append("expected value of type '").append(type).append("'");
if (what != null) {
builder.append(" for ").append(what);
}
builder.append(", but got ");
Printer.write(builder, value);
builder.append(" (").append(EvalUtils.getDataTypeName(value)).append(")");
return builder.toString();
}
public ConversionException(Type> type, Object value, @Nullable Object what) {
super(null, message(type, value, what));
}
public ConversionException(String message) {
super(null, message);
}
}
/********************************************************************
* *
* Subclasses *
* *
********************************************************************/
private static class ObjectType extends Type