Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add causes parameter to fail and use it for extension tags #17889

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ private ModuleExtensionContext createContext(
StarlarkBazelModule.create(
abridgedModule,
extension,
extensionId,
usagesValue.getRepoMappings().get(moduleKey),
usagesValue.getExtensionUsages().get(moduleKey)));
} catch (ExternalDepsException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ private StarlarkBazelModule(String name, String version, Tags tags, boolean isRo
public static StarlarkBazelModule create(
AbridgedModule module,
ModuleExtension extension,
ModuleExtensionId extensionId,
RepositoryMapping repoMapping,
@Nullable ModuleExtensionUsage usage)
throws ExternalDepsException {
Expand Down Expand Up @@ -130,7 +131,7 @@ public static StarlarkBazelModule create(
// (for example, String to Label).
typeCheckedTags
.get(tag.getTagName())
.add(TypeCheckedTag.create(tagClass, tag, labelConverter));
.add(TypeCheckedTag.create(tagClass, tag, labelConverter, tag.getTagName(), extensionId));
}
return new StarlarkBazelModule(
module.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,42 @@
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.FailureCause;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.Structure;
import net.starlark.java.spelling.SpellChecker;
import net.starlark.java.syntax.Location;

/**
* A {@link Tag} whose attribute values have been type-checked against the attribute schema define
* in the {@link TagClass}.
*/
@StarlarkBuiltin(name = "bazel_module_tag", documented = false)
public class TypeCheckedTag implements Structure {
public class TypeCheckedTag implements FailureCause, Structure {

private final TagClass tagClass;
private final Object[] attrValues;

private TypeCheckedTag(TagClass tagClass, Object[] attrValues) {
// The properties below are only used for error reporting.
private final Location location;
private final String tagClassName;
private final String extensionName;

private TypeCheckedTag(TagClass tagClass, Object[] attrValues, Location location,
String tagClassName, String extensionName) {
this.tagClass = tagClass;
this.attrValues = attrValues;
this.location = location;
this.tagClassName = tagClassName;
this.extensionName = extensionName;
}

/** Creates a {@link TypeCheckedTag}. */
public static TypeCheckedTag create(TagClass tagClass, Tag tag, LabelConverter labelConverter)
/**
* Creates a {@link TypeCheckedTag}.
*/
public static TypeCheckedTag create(TagClass tagClass, Tag tag, LabelConverter labelConverter,
String tagClassName, ModuleExtensionId extensionId)
throws ExternalDepsException {
Object[] attrValues = new Object[tagClass.getAttributes().size()];
for (Map.Entry<String, Object> attrValue : tag.getAttributeValues().entrySet()) {
Expand Down Expand Up @@ -95,7 +112,8 @@ public static TypeCheckedTag create(TagClass tagClass, Tag tag, LabelConverter l
attrValues[i] = Attribute.valueToStarlark(attr.getDefaultValueUnchecked());
}
}
return new TypeCheckedTag(tagClass, attrValues);
return new TypeCheckedTag(tagClass, attrValues, tag.getLocation(), tagClassName,
extensionId.getExtensionName());
}

@Override
Expand Down Expand Up @@ -123,4 +141,28 @@ public ImmutableCollection<String> getFieldNames() {
public String getErrorMessageForUnknownField(String field) {
return "unknown attribute " + field;
}

@Override
public void debugPrint(Printer printer, StarlarkSemantics semantics) {
printer.append(extensionName);
printer.append('.');
printer.append(tagClassName);
printer.append("(\n");

for (int i = 0; i < attrValues.length; i++) {
Attribute attr = tagClass.getAttributes().get(i);
printer.append(" ");
printer.append(attr.getPublicName());
printer.append(" = ");
printer.repr(attrValues[i]);
printer.append(",\n");
}

printer.append(")");
}

@Override
public Location getLocation() {
return location;
}
}
2 changes: 2 additions & 0 deletions src/main/java/net/starlark/java/eval/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ java_library(
"Eval.java",
"EvalException.java",
"EvalUtils.java",
"FailureCause.java",
"FlagGuardedValue.java",
"FormatParser.java",
"GuardedValue.java",
Expand All @@ -40,6 +41,7 @@ java_library(
"RangeList.java",
"RegularTuple.java",
"Sequence.java",
"SilentCloseable.java",
"SingletonTuple.java",
"Starlark.java",
"StarlarkCallable.java",
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/net/starlark/java/eval/EvalException.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@
/** An EvalException indicates an Starlark evaluation error. */
public class EvalException extends Exception {

private static final ImmutableList<StarlarkThread.CallStackEntry> NO_CALLSTACK_SENTINEL =
ImmutableList.of();

// The call stack associated with this error.
// It is initially null, but is set by the interpreter to a non-empty
// stack when popping a frame. Thus an exception newly created by a
// built-in function has no stack until it is thrown out of a function call.
@Nullable private ImmutableList<StarlarkThread.CallStackEntry> callstack;

/** Constructs an EvalException. Use {@link Starlak#errorf} if you want string formatting. */
/** Constructs an EvalException. Use {@link Starlark#errorf} if you want string formatting. */
public EvalException(String message) {
this(message, /*cause=*/ null);
}
Expand All @@ -57,6 +60,12 @@ public EvalException(Throwable cause) {
super(getCauseMessage(cause), cause);
}

static EvalException createWithoutCallStack(String message) {
EvalException e = new EvalException(message);
e.callstack = NO_CALLSTACK_SENTINEL;
return e;
}

private static String getCauseMessage(Throwable cause) {
String msg = cause.getMessage();
return msg != null ? msg : cause.toString();
Expand Down Expand Up @@ -107,7 +116,7 @@ public final String getMessageWithStack() {
* source line for each stack frame is obtained from the provided SourceReader.
*/
public final String getMessageWithStack(SourceReader src) {
if (callstack != null) {
if (callstack != null && callstack != NO_CALLSTACK_SENTINEL) {
return formatCallStack(callstack, getMessage(), src);
}
return getMessage();
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/net/starlark/java/eval/FailureCause.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.starlark.java.eval;

import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.syntax.Location;

/**
* A {@link StarlarkValue} that can be used as a cause in calls to {@code link}.
*
* <p>Implementations should override {@link StarlarkValue#debugPrint(Printer, StarlarkSemantics)}
* and ensure that, combined with {@link FailureCause#getLocation()}, this provides sufficient
* information for users to understand the cause of a failure without a stack trace.
*/
@StarlarkBuiltin(name = "failure_cause",
doc = "Can be passed to the <code>cause</code> parameter of "
+ "<a href=\"globals.html#fail\"><code>fail</code></a> to add context to an error message.")
public interface FailureCause extends StarlarkValue {

Location getLocation();

default void appendCauseTo(Printer printer, StarlarkSemantics semantics) {
printer.append(Starlark.type(this));
Location location = getLocation();
printer.append(" in file \"");
printer.append(location.file());
printer.append('\"');
if (location.line() != 0) {
printer.append(", line ");
printer.append(location.line());
if (location.column() != 0) {
printer.append(", column ");
printer.append(location.column());
}
}
printer.append(", with value:\n");
try (SilentCloseable ignored = printer.indent()) {
debugPrint(printer, semantics);
}
}
}
29 changes: 27 additions & 2 deletions src/main/java/net/starlark/java/eval/MethodLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,16 @@ public StarlarkList<?> dir(Object object, StarlarkThread thread) throws EvalExce
"Deprecated. Causes an optional prefix containing this string to be added to the"
+ " error message.",
positional = false,
named = true),
@Param(
name = "causes",
allowedTypes = {
@ParamType(type = Sequence.class, generic1 = FailureCause.class),
},
defaultValue = "[]",
doc = "A list of <code>failure_causes</code> to append to the error message. If this "
+ "list is not empty, no stack trace is printed.",
positional = false,
named = true)
},
extraPositionals =
Expand All @@ -682,8 +692,9 @@ public StarlarkList<?> dir(Object object, StarlarkThread thread) throws EvalExce
"A list of values, formatted with str and joined with spaces, that appear in the"
+ " error message."),
useStarlarkThread = true)
public void fail(Object msg, Object attr, Tuple args, StarlarkThread thread)
public void fail(Object msg, Object attr, Sequence<?> contexts0, Tuple args, StarlarkThread thread)
throws EvalException {
Sequence<FailureCause> causes = Sequence.cast(contexts0, FailureCause.class, "causes");
List<String> elems = new ArrayList<>();
// msg acts like a leading element of args.
if (msg != Starlark.NONE) {
Expand All @@ -696,7 +707,21 @@ public void fail(Object msg, Object attr, Tuple args, StarlarkThread thread)
if (attr != Starlark.NONE) {
str = String.format("attribute %s: %s", attr, str);
}
throw Starlark.errorf("%s", str);

if (causes.isEmpty()) {
throw Starlark.errorf("%s", str);
} else {
Printer p = new Printer();
p.append(str);
p.append("\nCauses:");
try (SilentCloseable ignored = p.indent()) {
for (FailureCause cause : causes) {
p.append('\n');
cause.appendCauseTo(p, thread.getSemantics());
}
}
throw EvalException.createWithoutCallStack(p.toString());
}
}

@StarlarkMethod(
Expand Down
31 changes: 28 additions & 3 deletions src/main/java/net/starlark/java/eval/Printer.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package net.starlark.java.eval;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.List;
Expand All @@ -36,6 +37,7 @@ public class Printer {
// indicating a cycle.
private Object[] stack;
private int depth;
private String indent = "";

/** Creates a printer that writes to the given buffer. */
public Printer(StringBuilder buffer) {
Expand All @@ -47,24 +49,47 @@ public Printer() {
this(new StringBuilder());
}

/** Increases the indent level by one tab.
* Should be called at the beginning of a new line.
*/
@CheckReturnValue
public final SilentCloseable indent() {
indent += '\t';
buffer.append('\t');
return () -> indent = indent.substring(0, indent.length() - 1);
}

/** Appends a char to the printer's buffer */
@CanIgnoreReturnValue
public final Printer append(char c) {
buffer.append(c);
if (c == '\n') {
buffer.append(indent);
}
return this;
}

/** Appends a char sequence to the printer's buffer */
@CanIgnoreReturnValue
public final Printer append(CharSequence s) {
buffer.append(s);
return this;
if (indent.isEmpty()) {
buffer.append(s);
return this;
} else {
return append(s, 0, s.length());
}
}

/** Appends a char subsequence to the printer's buffer */
@CanIgnoreReturnValue
public final Printer append(CharSequence s, int start, int end) {
buffer.append(s, start, end);
if (indent.isEmpty()) {
buffer.append(s, start, end);
} else {
for (int i = start; i < end; i++) {
append(s.charAt(i));
}
}
return this;
}

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/net/starlark/java/eval/SilentCloseable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.starlark.java.eval;

public interface SilentCloseable extends AutoCloseable {
@Override
void close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ public void basic() throws Exception {

StarlarkBazelModule moduleProxy =
StarlarkBazelModule.create(
abridgedModule, extension, module.getRepoMappingWithBazelDepsOnly(), usage);
abridgedModule, extension,
ModuleExtensionId.create(Label.parseCanonicalUnchecked("@foo//:extensions.bzl"), "ext"),
module.getRepoMappingWithBazelDepsOnly(), usage);

assertThat(moduleProxy.getName()).isEqualTo("foo");
assertThat(moduleProxy.getVersion()).isEqualTo("1.0");
Expand Down Expand Up @@ -136,7 +138,11 @@ public void unknownTagClass() throws Exception {
ExternalDepsException.class,
() ->
StarlarkBazelModule.create(
abridgedModule, extension, module.getRepoMappingWithBazelDepsOnly(), usage));
abridgedModule, extension,
ModuleExtensionId.create(Label.parseCanonicalUnchecked("@foo//:extensions.bzl"),
"ext"),
module.getRepoMappingWithBazelDepsOnly(),
usage));
assertThat(e).hasMessageThat().contains("does not have a tag class named blep");
}
}
Loading
Loading