Skip to content

Commit

Permalink
Add causes parameter to fail
Browse files Browse the repository at this point in the history
The new parameter makes it possiblt to fail in a way that is more
appropriate for user errors: No Starlark stack trace is prepended to the
failure, instead the provided causes and their locations are appended to
the error message.
  • Loading branch information
fmeum committed Mar 28, 2023
1 parent bc7a2a6 commit 9b8a063
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 4 deletions.
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

0 comments on commit 9b8a063

Please sign in to comment.