diff options
Diffstat (limited to 'src/main/java/com')
10 files changed, 434 insertions, 318 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java index 9c229018b0..247ea4d98a 100644 --- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java +++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java @@ -23,8 +23,8 @@ import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; 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.skylarkinterface.SkylarkPrintableValue; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; +import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.StringCanonicalizer; import com.google.devtools.build.lib.util.StringUtilities; @@ -51,7 +51,7 @@ import javax.annotation.Nullable; ) @Immutable @ThreadSafe -public final class Label implements Comparable<Label>, Serializable, SkylarkPrintableValue, SkyKey { +public final class Label implements Comparable<Label>, Serializable, SkylarkValue, SkyKey { public static final PathFragment EXTERNAL_PACKAGE_NAME = PathFragment.create("external"); public static final PathFragment EXTERNAL_PACKAGE_FILE_NAME = PathFragment.create("WORKSPACE"); public static final String DEFAULT_REPOSITORY_DIRECTORY = "__main__"; @@ -562,8 +562,20 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkPrin } @Override + public void reprLegacy(SkylarkPrinter printer) { + printer.repr(getCanonicalForm()); + } + + @Override public void repr(SkylarkPrinter printer) { + printer.append("Label("); printer.repr(getCanonicalForm()); + printer.append(")"); + } + + @Override + public void strLegacy(SkylarkPrinter printer) { + printer.append(getCanonicalForm()); } @Override diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java index 29cfb47c57..acc5a54644 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java @@ -466,7 +466,8 @@ public abstract class OutputFormatter implements Serializable { if (attributeMap.isConfigurable(attr.getName())) { // We don't know the actual value for configurable attributes, so we reconstruct // the select without trying to resolve it. - printStream.printf(outputAttributePattern, + printStream.printf( + outputAttributePattern, attr.getPublicName(), outputConfigurableAttrValue(rule, attributeMap, attr)); continue; @@ -479,20 +480,17 @@ public abstract class OutputFormatter implements Serializable { // Computed defaults that depend on configurable attributes can have multiple values. continue; } - printStream.printf(outputAttributePattern, + printStream.printf( + outputAttributePattern, attr.getPublicName(), outputAttrValue(Iterables.getOnlyElement(values))); } printStream.printf(")\n%s", lineTerm); } - /** - * Returns the given attribute value with BUILD output syntax. Does not support selects. - */ + /** Returns the given attribute value with BUILD output syntax. Does not support selects. */ private String outputAttrValue(Object value) { - if (value instanceof Label) { - value = ((Label) value).getDefaultCanonicalForm(); - } else if (value instanceof License) { + if (value instanceof License) { List<String> licenseTypes = new ArrayList<>(); for (License.LicenseType licenseType : ((License) value).getLicenseTypes()) { licenseTypes.add(licenseType.toString().toLowerCase()); @@ -504,7 +502,7 @@ public abstract class OutputFormatter implements Serializable { } else if (value instanceof TriState) { value = ((TriState) value).toInt(); } - return Printer.repr(value); + return new LabelPrinter().repr(value).toString(); } /** @@ -513,14 +511,16 @@ public abstract class OutputFormatter implements Serializable { * <p>Since query doesn't know which select path should be chosen, this doesn't try to * resolve the final value. Instead it just reconstructs the select. */ - private String outputConfigurableAttrValue(Rule rule, RawAttributeMapper attributeMap, - Attribute attr) { + private String outputConfigurableAttrValue( + Rule rule, RawAttributeMapper attributeMap, Attribute attr) { List<String> selectors = new ArrayList<>(); - for (BuildType.Selector<?> selector : ((BuildType.SelectorList<?>) - attributeMap.getRawAttributeValue(rule, attr)).getSelectors()) { + for (BuildType.Selector<?> selector : + ((BuildType.SelectorList<?>) attributeMap.getRawAttributeValue(rule, attr)) + .getSelectors()) { if (selector.isUnconditional()) { - selectors.add(outputAttrValue( - Iterables.getOnlyElement(selector.getEntries().entrySet()).getValue())); + selectors.add( + outputAttrValue( + Iterables.getOnlyElement(selector.getEntries().entrySet()).getValue())); } else { selectors.add(String.format("select(%s)", outputAttrValue(selector.getEntries()))); } @@ -834,4 +834,16 @@ public abstract class OutputFormatter implements Serializable { target.getPackage().getNameFragment()) : location.print(); } + + private static class LabelPrinter extends Printer.BasePrinter { + @Override + public LabelPrinter repr(Object o) { + if (o instanceof Label) { + writeString(((Label) o).getCanonicalForm()); + } else { + super.repr(o); + } + return this; + } + } } diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkPrintableValue.java b/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkPrintableValue.java deleted file mode 100644 index 69426ea6c8..0000000000 --- a/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkPrintableValue.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015 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.skylarkinterface; - -/** - * A Skylark value that is represented by {@code str()} differently than by {@code repr()}. - */ -public interface SkylarkPrintableValue extends SkylarkValue { - /** - * Print an informal, human-readable representation of the value. - * - * @param printer a printer to be used for formatting nested values. - */ - void str(SkylarkPrinter printer); -} diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java b/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java index a7c8c2779b..bf6a5c5586 100644 --- a/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java +++ b/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java @@ -37,4 +37,38 @@ public interface SkylarkValue { * @param printer a printer to be used for formatting nested values. */ void repr(SkylarkPrinter printer); + + /** + * Print a legacy representation of object x. + * + * <p>By default dispatches to the {@code repr} method. Should be called instead of {@code repr} + * if --incompatible_descriptive_string_representations=false is used. + * + * @param printer an instance of a printer to be used for formatting nested values + */ + default void reprLegacy(SkylarkPrinter printer) { + repr(printer); + } + + /** + * Print an informal, human-readable representation of the value. + * + * <p>By default dispatches to the {@code repr} method. + * + * @param printer a printer to be used for formatting nested values. + */ + default void str(SkylarkPrinter printer) { + repr(printer); + } + + /** + * Print a legacy informal, human-readable representation of the value. + * + * <p>By default dispatches to the {@code reprLegacy} method. + * + * @param printer a printer to be used for formatting nested values. + */ + default void strLegacy(SkylarkPrinter printer) { + reprLegacy(printer); + } } 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 06e46dbbdb..6022768bee 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 @@ -84,7 +84,7 @@ public final class BinaryOperatorExpression extends Expression { } /** Implements the "in" operator. */ - private static boolean in(Object lval, Object rval, Location location, Environment env) + private static boolean in(Object lval, Object rval, Environment env, Location location) throws EvalException { if (env.getSemantics().incompatibleDepsetIsNotIterable && rval instanceof SkylarkNestedSet) { throw new EvalException( @@ -163,7 +163,7 @@ public final class BinaryOperatorExpression extends Expression { return divide(lval, rval, location); case PERCENT: - return percent(lval, rval, location); + return percent(lval, rval, env, location); case EQUALS_EQUALS: return lval.equals(rval); @@ -184,10 +184,10 @@ public final class BinaryOperatorExpression extends Expression { return compare(lval, rval, location) >= 0; case IN: - return in(lval, rval, location, env); + return in(lval, rval, env, location); case NOT_IN: - return !in(lval, rval, location, env); + return !in(lval, rval, env, location); default: throw new AssertionError("Unsupported binary operator: " + operator); @@ -352,7 +352,7 @@ public final class BinaryOperatorExpression extends Expression { } /** Implements Operator.PERCENT. */ - private static Object percent(Object lval, Object rval, Location location) + private static Object percent(Object lval, Object rval, Environment env, Location location) throws EvalException { // int % int if (lval instanceof Integer && rval instanceof Integer) { @@ -376,9 +376,9 @@ public final class BinaryOperatorExpression extends Expression { String pattern = (String) lval; try { if (rval instanceof Tuple) { - return Printer.formatWithList(pattern, (Tuple) rval); + return Printer.getPrinter(env).formatWithList(pattern, (Tuple) rval).toString(); } - return Printer.format(pattern, rval); + return Printer.getPrinter(env).format(pattern, rval).toString(); } catch (IllegalFormatException e) { throw new EvalException(location, e.getMessage()); } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FormatParser.java b/src/main/java/com/google/devtools/build/lib/syntax/FormatParser.java index d4a2323994..bc3338add6 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/FormatParser.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/FormatParser.java @@ -30,9 +30,11 @@ public final class FormatParser { private static final ImmutableSet<Character> ILLEGAL_IN_FIELD = ImmutableSet.of('.', '[', ']', ','); + private final Environment environment; private final Location location; - public FormatParser(Location location) { + public FormatParser(Environment environment, Location location) { + this.environment = environment; this.location = location; } @@ -91,7 +93,7 @@ public final class FormatParser { History history, StringBuilder output) throws EvalException { - BasePrinter printer = Printer.getPrinter(output); + BasePrinter printer = Printer.getPrinter(environment, output); if (has(chars, pos + 1, '{')) { // Escaped brace -> output and move to char after right brace printer.append("{"); diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java index 6caf43fc2f..3ab4e4d71a 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java @@ -29,7 +29,6 @@ import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause; -import com.google.devtools.build.lib.syntax.Printer.BasePrinter; import com.google.devtools.build.lib.syntax.Runtime.NoneType; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.Preconditions; @@ -271,7 +270,7 @@ public final class FuncallExpression extends Expression { @Override public String toString() { - BasePrinter printer = Printer.getPrinter(); + Printer.LengthLimitedPrinter printer = new Printer.LengthLimitedPrinter(); if (obj != null) { printer.append(obj.toString()).append("."); } 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 5c5905b9f4..e549699287 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 @@ -963,7 +963,7 @@ public class MethodLibrary { Location loc, Environment env) throws EvalException { - return new FormatParser(loc) + return new FormatParser(env, loc) .format( self, args.getImmutableList(), @@ -1619,12 +1619,13 @@ public class MethodLibrary { doc = "Converts any object to string. This is useful for debugging." + "<pre class=\"language-python\">str(\"ab\") == \"ab\"</pre>", - parameters = {@Param(name = "x", doc = "The object to convert.")} + parameters = {@Param(name = "x", doc = "The object to convert.")}, + useEnvironment = true ) private static final BuiltinFunction str = new BuiltinFunction("str") { - public String invoke(Object x) { - return Printer.getPrinter().str(x).toString(); + public String invoke(Object x, Environment env) { + return Printer.getPrinter(env).str(x).toString(); } }; @@ -1634,12 +1635,13 @@ public class MethodLibrary { doc = "Converts any object to a string representation. This is useful for debugging.<br>" + "<pre class=\"language-python\">str(\"ab\") == \\\"ab\\\"</pre>", - parameters = {@Param(name = "x", doc = "The object to convert.")} + parameters = {@Param(name = "x", doc = "The object to convert.")}, + useEnvironment = true ) private static final BuiltinFunction repr = new BuiltinFunction("repr") { - public String invoke(Object x) { - return Printer.getPrinter().repr(x).toString(); + public String invoke(Object x, Environment env) { + return Printer.getPrinter(env).repr(x).toString(); } }; @@ -2104,11 +2106,14 @@ public class MethodLibrary { public Runtime.NoneType invoke( String sep, SkylarkList<?> starargs, Location loc, Environment env) throws EvalException { - String msg = starargs.stream().map(Printer::str).collect(joining(sep)); + String msg = + starargs + .stream() + .map((Object o) -> Printer.getPrinter(env).str(o).toString()) + .collect(joining(sep)); // As part of the integration test "skylark_flag_test.sh", if the // "--internal_skylark_flag_test_canary" flag is enabled, append an extra marker string to - // the - // output. + // the output. if (env.getSemantics().skylarkFlagTestCanary) { msg += "<== skylark flag test ==>"; } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java index 0a581777d8..12cc7929f4 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.events.Location; -import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintableValue; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; @@ -51,22 +50,53 @@ public class Printer { public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH = 32; /** - * Creates an instance of BasePrinter that wraps an existing buffer. - * @param buffer an Appendable - * @return new BasePrinter + * Creates an instance of {@link BasePrinter} that wraps an existing buffer. + * + * @param buffer an {@link Appendable} + * @return new {@link BasePrinter} */ - public static BasePrinter getPrinter(Appendable buffer) { + static BasePrinter getPrinter(Appendable buffer) { return new BasePrinter(buffer); } /** - * Creates an instance of BasePrinter with an empty buffer. - * @return new BasePrinter + * Creates an instance of {@link BasePrinter} with an empty buffer. + * + * @return new {@link BasePrinter} */ public static BasePrinter getPrinter() { return getPrinter(new StringBuilder()); } + /** + * Creates an instance of BasePrinter with a given buffer. + * + * @param env {@link Environment} + * @param buffer an {@link Appendable} + * @return new BasePrinter + */ + static BasePrinter getPrinter(Environment env, Appendable buffer) { + if (env.getSemantics().incompatibleDescriptiveStringRepresentations) { + return new BasePrinter(buffer); + } else { + return new LegacyPrinter(buffer); + } + } + + /** + * Creates an instance of BasePrinter with an empty buffer. + * + * @param env {@link Environment} + * @return new BasePrinter + */ + static BasePrinter getPrinter(Environment env) { + if (env.getSemantics().incompatibleDescriptiveStringRepresentations) { + return new BasePrinter(); + } else { + return new LegacyPrinter(); + } + } + private Printer() {} // These static methods proxy to the similar methods of BasePrinter @@ -111,7 +141,7 @@ public class Printer { @Nullable String singletonTerminator, int maxItemsToPrint, int criticalItemsStringLength) { - return getPrinter() + return new LengthLimitedPrinter() .printAbbreviatedList( list, before, @@ -164,7 +194,7 @@ public class Printer { boolean isTuple, int maxItemsToPrint, int criticalItemsStringLength) { - return getPrinter() + return new LengthLimitedPrinter() .printAbbreviatedList(list, isTuple, maxItemsToPrint, criticalItemsStringLength) .toString(); } @@ -256,158 +286,26 @@ public class Printer { } } - /** - * Helper class for {@code Appendable}s that want to limit the length of their input. - * - * <p>Instances of this class act as a proxy for one {@code Appendable} object and decide whether - * the given input (or parts of it) can be written to the underlying {@code Appendable}, depending - * on whether the specified maximum length has been met or not. - */ - private static final class LengthLimitedAppendable implements Appendable { - - private static final ImmutableSet<Character> SPECIAL_CHARS = - ImmutableSet.of(',', ' ', '"', '\'', ':', '(', ')', '[', ']', '{', '}'); - - private static final Pattern ARGS_PATTERN = Pattern.compile("<\\d+ more arguments>"); - - private final Appendable original; - private int limit; - private boolean ignoreLimit; - private boolean previouslyShortened; - - private LengthLimitedAppendable(Appendable original, int limit) { - this.original = original; - this.limit = limit; - } - - private static LengthLimitedAppendable create(Appendable original, int limit) { - // We don't want to overwrite the limit if original is already an instance of this class. - return (original instanceof LengthLimitedAppendable) - ? (LengthLimitedAppendable) original : new LengthLimitedAppendable(original, limit); - } - - @Override - public Appendable append(CharSequence csq) throws IOException { - if (ignoreLimit || hasOnlySpecialChars(csq)) { - // Don't update limit. - original.append(csq); - previouslyShortened = false; - } else { - int length = csq.length(); - if (length <= limit) { - limit -= length; - original.append(csq); - } else { - original.append(csq, 0, limit); - // We don't want to append multiple ellipses. - if (!previouslyShortened) { - original.append("..."); - } - appendTrailingSpecialChars(csq, limit); - previouslyShortened = true; - limit = 0; - } - } - return this; - } - - /** - * Appends any trailing "special characters" (e.g. brackets, quotation marks) in the given - * sequence to the output buffer, regardless of the limit. - * - * <p>For example, let's look at foo(['too long']). Without this method, the shortened result - * would be foo(['too...) instead of the prettier foo(['too...']). - * - * <p>If the input string was already shortened and contains "<x more arguments>", this part - * will also be appended. - */ - // TODO(bazel-team): Given an input list - // - // [1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6] - // - // the inner list gets doubly mangled as - // - // [1, 2, 3, [10, 20, 30, 40, <2 more argu...<2 more arguments>], <3 more arguments>] - private void appendTrailingSpecialChars(CharSequence csq, int limit) throws IOException { - int length = csq.length(); - Matcher matcher = ARGS_PATTERN.matcher(csq); - // We assume that everything following the "x more arguments" part has to be copied, too. - int start = matcher.find() ? matcher.start() : length; - // Find the left-most non-arg char that has to be copied. - for (int i = start - 1; i > limit; --i) { - if (isSpecialChar(csq.charAt(i))) { - start = i; - } else { - break; - } - } - if (start < length) { - original.append(csq, start, csq.length()); - } - } - - /** - * Returns whether the given sequence denotes characters that are not part of the value of an - * argument. - * - * <p>Examples are brackets, braces and quotation marks. - */ - private boolean hasOnlySpecialChars(CharSequence csq) { - for (int i = 0; i < csq.length(); ++i) { - if (!isSpecialChar(csq.charAt(i))) { - return false; - } - } - return true; - } - - private boolean isSpecialChar(char c) { - return SPECIAL_CHARS.contains(c); - } - - @Override - public Appendable append(CharSequence csq, int start, int end) throws IOException { - return this.append(csq.subSequence(start, end)); - } - - @Override - public Appendable append(char c) throws IOException { - return this.append(String.valueOf(c)); - } - - public boolean hasHitLimit() { - return limit <= 0; - } - - public void enforceLimit() { - ignoreLimit = false; - } - - public void ignoreLimit() { - ignoreLimit = true; - } - - @Override - public String toString() { - return original.toString(); - } - } - /** Actual class that implements Printer API */ - public static final class BasePrinter implements SkylarkPrinter { + public static class BasePrinter implements SkylarkPrinter { // Methods of this class should not recurse through static methods of Printer - private final Appendable buffer; + protected final Appendable buffer; /** * Creates a printer instance. * - * @param buffer the Appendable to which to print the representation + * @param buffer the {@link Appendable} to which to print the representation */ - private BasePrinter(Appendable buffer) { + protected BasePrinter(Appendable buffer) { this.buffer = buffer; } + /** Creates a printer instance with a new StringBuilder. */ + protected BasePrinter() { + this.buffer = new StringBuilder(); + } + @Override public String toString() { return buffer.toString(); @@ -421,8 +319,8 @@ public class Printer { * @return the buffer, in fluent style */ public BasePrinter str(Object o) { - if (o instanceof SkylarkPrintableValue) { - ((SkylarkPrintableValue) o).str(this); + if (o instanceof SkylarkValue) { + ((SkylarkValue) o).str(this); return this; } @@ -496,9 +394,9 @@ public class Printer { * Write a properly escaped Skylark representation of a string to a buffer. * * @param s the string a representation of which to repr. - * @return the Appendable, in fluent style. + * @return this printer. */ - private BasePrinter writeString(String s) { + protected BasePrinter writeString(String s) { this.append(SKYLARK_QUOTATION_MARK); int len = s.length(); for (int i = 0; i < len; i++) { @@ -544,7 +442,7 @@ public class Printer { * @param singletonTerminator null or a string to print after the list if it is a singleton The * singleton case is notably relied upon in python syntax to distinguish a tuple of size one * such as ("foo",) from a merely parenthesized object such as ("foo"). - * @return the BasePrinter. + * @return this printer. */ @Override public BasePrinter printList( @@ -553,48 +451,9 @@ public class Printer { String separator, String after, @Nullable String singletonTerminator) { - return printAbbreviatedList(list, before, separator, after, singletonTerminator, -1, -1); - } - /** - * Print a list of object representations. - * - * <p>The length of the output will be limited when both {@code maxItemsToPrint} and {@code - * criticalItemsStringLength} have values greater than zero. - * - * @param list the list of objects to repr (each as with repr) - * @param before a string to print before the list - * @param separator a separator to print between each object - * @param after a string to print after the list - * @param singletonTerminator null or a string to print after the list if it is a singleton The - * singleton case is notably relied upon in python syntax to distinguish a tuple of size one - * such as ("foo",) from a merely parenthesized object such as ("foo"). - * @param maxItemsToPrint the maximum number of elements to be printed. - * @param criticalItemsStringLength a soft limit for the total string length of all arguments. - * 'Soft' means that this limit may be exceeded because of formatting. - * @return the BasePrinter. - */ - public BasePrinter printAbbreviatedList( - Iterable<?> list, - String before, - String separator, - String after, - @Nullable String singletonTerminator, - int maxItemsToPrint, - int criticalItemsStringLength) { this.append(before); - int len = 0; - // Limits the total length of the string representation of the elements, if specified. - if (maxItemsToPrint > 0 && criticalItemsStringLength > 0) { - len = - appendListElements( - LengthLimitedAppendable.create(buffer, criticalItemsStringLength), - list, - separator, - maxItemsToPrint); - } else { - len = appendListElements(list, separator); - } + int len = appendListElements(list, separator); if (singletonTerminator != null && len == 1) { this.append(singletonTerminator); } @@ -620,72 +479,23 @@ public class Printer { } /** - * Tries to append the given elements to the specified {@link Appendable} until specific limits - * are reached. - * - * @return the number of appended elements. - */ - private int appendListElements( - LengthLimitedAppendable appendable, - Iterable<?> list, - String separator, - int maxItemsToPrint) { - boolean printSeparator = false; // don't print the separator before the first element - boolean skipArgs = false; - int items = Iterables.size(list); - int len = 0; - // We don't want to print "1 more arguments", hence we don't skip arguments if there is only - // one above the limit. - int itemsToPrint = (items - maxItemsToPrint == 1) ? items : maxItemsToPrint; - appendable.enforceLimit(); - for (Object o : list) { - // We don't want to print "1 more arguments", even if we hit the string limit. - if (len == itemsToPrint || (appendable.hasHitLimit() && len < items - 1)) { - skipArgs = true; - break; - } - if (printSeparator) { - this.append(separator); - } - Printer.getPrinter(appendable).repr(o); - printSeparator = true; - len++; - } - appendable.ignoreLimit(); - if (skipArgs) { - this.append(separator); - this.append(String.format("<%d more arguments>", items - len)); - } - return len; - } - - /** * Print a Skylark list or tuple of object representations * * @param list the contents of the list or tuple * @param isTuple if true the list will be formatted with parentheses and with a trailing comma - * in case of one-element tuples. - * @param maxItemsToPrint the maximum number of elements to be printed. - * @param criticalItemsStringLength a soft limit for the total string length of all arguments. - * 'Soft' means that this limit may be exceeded because of formatting. - * @return the Appendable, in fluent style. + * in case of one-element tuples. 'Soft' means that this limit may be exceeded because of + * formatting. + * @return this printer. */ - public BasePrinter printAbbreviatedList( - Iterable<?> list, boolean isTuple, int maxItemsToPrint, int criticalItemsStringLength) { + @Override + public BasePrinter printList(Iterable<?> list, boolean isTuple) { if (isTuple) { - return this.printAbbreviatedList(list, "(", ", ", ")", ",", - maxItemsToPrint, criticalItemsStringLength); + return this.printList(list, "(", ", ", ")", ","); } else { - return this.printAbbreviatedList(list, "[", ", ", "]", null, - maxItemsToPrint, criticalItemsStringLength); + return this.printList(list, "[", ", ", "]", null); } } - @Override - public BasePrinter printList(Iterable<?> list, boolean isTuple) { - return this.printAbbreviatedList(list, isTuple, -1, -1); - } - /** * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %% * are supported. @@ -743,9 +553,9 @@ public class Printer { if (a >= argLength) { throw new MissingFormatWidthException( "not enough arguments for format pattern " - + this.repr(pattern) + + Printer.repr(pattern) + ": " - + this.repr(Tuple.copyOf(arguments))); + + Printer.repr(Tuple.copyOf(arguments))); } Object argument = arguments.get(a++); switch (directive) { @@ -755,7 +565,7 @@ public class Printer { continue; } else { throw new MissingFormatWidthException( - "invalid argument " + this.repr(argument) + " for format pattern %d"); + "invalid argument " + Printer.repr(argument) + " for format pattern %d"); } case 'r': this.repr(argument); @@ -791,5 +601,258 @@ public class Printer { Printer.append(buffer, s); return this; } + + BasePrinter append(CharSequence sequence, int start, int end) { + return this.append(sequence.subSequence(start, end)); + } + } + + /** A version of BasePrinter that renders object in old style for compatibility reasons. */ + static final class LegacyPrinter extends BasePrinter { + protected LegacyPrinter() { + super(); + } + + protected LegacyPrinter(Appendable buffer) { + super(buffer); + } + + @Override + public LegacyPrinter repr(Object o) { + if (o instanceof SkylarkValue) { + ((SkylarkValue) o).reprLegacy(this); + } else { + super.repr(o); + } + return this; + } + + @Override + public LegacyPrinter str(Object o) { + if (o instanceof SkylarkValue) { + ((SkylarkValue) o).strLegacy(this); + } else { + super.str(o); + } + return this; + } + } + + /** A version of {@code BasePrinter} that is able to print abbreviated lists. */ + public static final class LengthLimitedPrinter extends BasePrinter { + + private static final ImmutableSet<Character> SPECIAL_CHARS = + ImmutableSet.of(',', ' ', '"', '\'', ':', '(', ')', '[', ']', '{', '}'); + + private static final Pattern ARGS_PATTERN = Pattern.compile("<\\d+ more arguments>"); + + // Limits can be set several times recursively and then unset the same amount of times. + // But in fact they should be set only the first time and unset only the last time. + // To achieve that we need to keep track of the recursion depth. + private int recursionDepth; + // Current limit of symbols to print in the limited mode (`ignoreLimit = false`). + private int limit; + private boolean ignoreLimit = true; + private boolean previouslyShortened; + + /** + * Print a list of object representations. + * + * <p>The length of the output will be limited when both {@code maxItemsToPrint} and {@code + * criticalItemsStringLength} have values greater than zero. + * + * @param list the list of objects to repr (each as with repr) + * @param before a string to print before the list + * @param separator a separator to print between each object + * @param after a string to print after the list + * @param singletonTerminator null or a string to print after the list if it is a singleton The + * singleton case is notably relied upon in python syntax to distinguish a tuple of size one + * such as ("foo",) from a merely parenthesized object such as ("foo"). + * @param maxItemsToPrint the maximum number of elements to be printed. + * @param criticalItemsStringLength a soft limit for the total string length of all arguments. + * 'Soft' means that this limit may be exceeded because of formatting. + * @return the BasePrinter. + */ + LengthLimitedPrinter printAbbreviatedList( + Iterable<?> list, + String before, + String separator, + String after, + @Nullable String singletonTerminator, + int maxItemsToPrint, + int criticalItemsStringLength) { + this.append(before); + int len = appendListElements(list, separator, maxItemsToPrint, criticalItemsStringLength); + if (singletonTerminator != null && len == 1) { + this.append(singletonTerminator); + } + return this.append(after); + } + + /** + * Print a Skylark list or tuple of object representations + * + * @param list the contents of the list or tuple + * @param isTuple if true the list will be formatted with parentheses and with a trailing comma + * in case of one-element tuples. + * @param maxItemsToPrint the maximum number of elements to be printed. + * @param criticalItemsStringLength a soft limit for the total string length of all arguments. + * 'Soft' means that this limit may be exceeded because of formatting. + * @return this printer. + */ + public LengthLimitedPrinter printAbbreviatedList( + Iterable<?> list, boolean isTuple, int maxItemsToPrint, int criticalItemsStringLength) { + if (isTuple) { + return this.printAbbreviatedList( + list, "(", ", ", ")", ",", maxItemsToPrint, criticalItemsStringLength); + } else { + return this.printAbbreviatedList( + list, "[", ", ", "]", null, maxItemsToPrint, criticalItemsStringLength); + } + } + + /** + * Tries to append the given elements to the specified {@link Appendable} until specific limits + * are reached. + * + * @return the number of appended elements. + */ + private int appendListElements( + Iterable<?> list, String separator, int maxItemsToPrint, int criticalItemsStringLength) { + boolean printSeparator = false; // don't print the separator before the first element + boolean skipArgs = false; + int items = Iterables.size(list); + int len = 0; + // We don't want to print "1 more arguments", hence we don't skip arguments if there is only + // one above the limit. + int itemsToPrint = (items - maxItemsToPrint == 1) ? items : maxItemsToPrint; + enforceLimit(criticalItemsStringLength); + for (Object o : list) { + // We don't want to print "1 more arguments", even if we hit the string limit. + if (len == itemsToPrint || (hasHitLimit() && len < items - 1)) { + skipArgs = true; + break; + } + if (printSeparator) { + this.append(separator); + } + this.repr(o); + printSeparator = true; + len++; + } + ignoreLimit(); + if (skipArgs) { + this.append(separator); + this.append(String.format("<%d more arguments>", items - len)); + } + return len; + } + + @Override + public LengthLimitedPrinter append(CharSequence csq) { + if (ignoreLimit || hasOnlySpecialChars(csq)) { + // Don't update limit. + Printer.append(buffer, csq); + previouslyShortened = false; + } else { + int length = csq.length(); + if (length <= limit) { + limit -= length; + Printer.append(buffer, csq); + } else { + Printer.append(buffer, csq, 0, limit); + // We don't want to append multiple ellipses. + if (!previouslyShortened) { + Printer.append(buffer, "..."); + } + appendTrailingSpecialChars(csq, limit); + previouslyShortened = true; + limit = 0; + } + } + return this; + } + + @Override + public LengthLimitedPrinter append(char c) { + // Use the local `append(sequence)` method so that limits can apply + return this.append(String.valueOf(c)); + } + + /** + * Appends any trailing "special characters" (e.g. brackets, quotation marks) in the given + * sequence to the output buffer, regardless of the limit. + * + * <p>For example, let's look at foo(['too long']). Without this method, the shortened result + * would be foo(['too...) instead of the prettier foo(['too...']). + * + * <p>If the input string was already shortened and contains "<x more arguments>", this part + * will also be appended. + */ + // TODO(bazel-team): Given an input list + // + // [1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6] + // + // the inner list gets doubly mangled as + // + // [1, 2, 3, [10, 20, 30, 40, <2 more argu...<2 more arguments>], <3 more arguments>] + private LengthLimitedPrinter appendTrailingSpecialChars(CharSequence csq, int limit) { + int length = csq.length(); + Matcher matcher = ARGS_PATTERN.matcher(csq); + // We assume that everything following the "x more arguments" part has to be copied, too. + int start = matcher.find() ? matcher.start() : length; + // Find the left-most non-arg char that has to be copied. + for (int i = start - 1; i > limit; --i) { + if (isSpecialChar(csq.charAt(i))) { + start = i; + } else { + break; + } + } + if (start < length) { + Printer.append(buffer, csq, start, csq.length()); + } + return this; + } + + /** + * Returns whether the given sequence denotes characters that are not part of the value of an + * argument. + * + * <p>Examples are brackets, braces and quotation marks. + */ + private boolean hasOnlySpecialChars(CharSequence csq) { + for (int i = 0; i < csq.length(); ++i) { + if (!isSpecialChar(csq.charAt(i))) { + return false; + } + } + return true; + } + + private boolean isSpecialChar(char c) { + return SPECIAL_CHARS.contains(c); + } + + boolean hasHitLimit() { + return limit <= 0; + } + + private void enforceLimit(int limit) { + ignoreLimit = false; + if (recursionDepth == 0) { + this.limit = limit; + ++recursionDepth; + } + } + + private void ignoreLimit() { + if (recursionDepth > 0) { + --recursionDepth; + } + if (recursionDepth == 0) { + ignoreLimit = true; + } + } } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java index 097f581ffb..d77fda83e1 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java @@ -195,4 +195,19 @@ public class SkylarkSemanticsOptions extends OptionsBase implements Serializable help = "If set to true, arithmetic operations throw an error in case of overflow/underflow." ) public boolean incompatibleCheckedArithmetic; + + @Option( + name = "incompatible_descriptive_string_representations", + defaultValue = "false", + category = "incompatible changes", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE}, + optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, + help = + "If set to true, objects are converted to strings by `str` and `repr` functions using the " + + "new style representations that are designed to be more descriptive and not to leak " + + "information that's not supposed to be exposed." + ) + public boolean incompatibleDescriptiveStringRepresentations; } |