Skip to content

Commit

Permalink
Implement Enso-specific assert (#7883)
Browse files Browse the repository at this point in the history
Implement Enso-specific assert - `Runtime.assert` that works like asserts in any other runtime.

# Important Notes
- Enso-specific assertions are enabled when JVM assertions are enabled, or when `ENSO_ENABLE_ASSERTIONS` env var is not empty (See https://github.com/enso-org/enso/blob/72cd8361cb337d34f7e917c0a6af18ea48e0e90b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java#L139)
  • Loading branch information
Akirathan authored Sep 29, 2023
1 parent 657be61 commit 9f15b90
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,7 @@
- [Always persist `TRACE` level logs to a file][7825]
- [Downloadable VSCode extension][7861]
- [New `project/status` route for reporting LS state][7801]
- [Add Enso-specific assertions][7883])

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
Expand Down Expand Up @@ -1111,6 +1112,7 @@
[7801]: https://github.com/enso-org/enso/pull/7801
[7825]: https://github.com/enso-org/enso/pull/7825
[7861]: https://github.com/enso-org/enso/pull/7861
[7883]: https://github.com/enso-org/enso/pull/7883

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ val tikaVersion = "2.4.1"
val typesafeConfigVersion = "1.4.2"
val junitVersion = "4.13.2"
val junitIfVersion = "0.13.2"
val hamcrestVersion = "1.3"
val netbeansApiVersion = "RELEASE180"
val fansiVersion = "0.4.0"

Expand Down Expand Up @@ -1336,6 +1337,7 @@ lazy val runtime = (project in file("engine/runtime"))
"org.typelevel" %% "cats-core" % catsVersion,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
"com.lihaoyi" %% "fansi" % fansiVersion
),
Compile / compile / compileInputs := (Compile / compile / compileInputs)
Expand Down
10 changes: 10 additions & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Common.enso
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ type Compile_Error
to_display_text : Text
to_display_text self = "Compile error: "+self.message+"."

@Builtin_Type
type Assertion_Error
## PRIVATE
Error message

## PRIVATE
Convert the Assertion_Error to a human-readable format.
to_display_text : Text
to_display_text self = "Assertion Error: '" + self.message.to_text + "'"

@Builtin_Type
type Inexhaustive_Pattern_Match
## PRIVATE
Expand Down
15 changes: 14 additions & 1 deletion distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import project.Data.Boolean.Boolean
import project.Data.Text.Case.Case
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Errors.Common.Assertion_Error
import project.Errors.Common.Forbidden_Operation
import project.Errors.Common.Type_Error
import project.Function.Function
Expand Down Expand Up @@ -52,8 +53,20 @@ get_stack_trace =
gc : Nothing
gc = @Builtin_Method "Runtime.gc"

## ADVANCED

Asserts that the given action succeeds, otherwise throws a panic.

Assertions are disable by default, meaning that call to this method is
a no-op. To enable assertions, set the environment variable
`ENSO_ENABLE_ASSERTIONS=true`
assert : Boolean -> Text -> Nothing ! Assertion_Error
assert ~action message="" = assert_builtin action message

## PRIVATE
ADVANCED
assert_builtin ~action message = @Builtin_Method "Runtime.assert_builtin"

## ADVANCED

Executes the provided action without allowing it to inline.

Expand Down
6 changes: 6 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,12 @@ configured correctly and run the tests as following:
LANG=C enso --run test/Tests
```

Note that JVM assertions are not enabled by default, one has to pass `-ea` via
`JAVA_OPTS` environment variable. There are also Enso-specific assertions
(method `Runtime.assert`) that can be enabled when `ENSO_ENABLE_ASSERTIONS`
environment variable is set to "true". If JVM assertions are enable, Enso
assertions are enabled as well.

#### Test Dependencies

To run all the stdlib test suites, set `CI=true` environment variable:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.enso.interpreter.node.expression.builtin.error;

import java.util.List;
import org.enso.interpreter.dsl.BuiltinType;
import org.enso.interpreter.node.expression.builtin.UniquelyConstructibleBuiltin;

@BuiltinType
public final class AssertionError extends UniquelyConstructibleBuiltin {
@Override
protected String getConstructorName() {
return "Error";
}

@Override
protected List<String> getConstructorParamNames() {
return List.of("message");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.enso.interpreter.node.expression.builtin.runtime;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.State;

@BuiltinMethod(
type = "Runtime",
name = "assert_builtin",
description = "Asserts that the given condition is true",
autoRegister = false)
public abstract class AssertNode extends Node {

public static AssertNode build() {
return AssertNodeGen.create();
}

public abstract Object execute(VirtualFrame frame, State state, @Suspend Object action, Text msg);

@Idempotent
protected boolean isAssertionsEnabled() {
return EnsoContext.get(this).isAssertionsEnabled();
}

@Specialization(guards = "!isAssertionsEnabled()")
Object doAssertionsDisabled(VirtualFrame frame, State state, Object action, Text msg) {
return EnsoContext.get(this).getNothing();
}

@Specialization(replaces = "doAssertionsDisabled")
Object doAssertionsEnabled(
VirtualFrame frame,
State state,
Object action,
Text msg,
@Cached("create()") ThunkExecutorNode thunkExecutorNode,
@Cached BranchProfile resultIsNotAtomProfile) {
var ctx = EnsoContext.get(this);
var builtins = ctx.getBuiltins();
Object actionRes =
thunkExecutorNode.executeThunk(frame, action, state, BaseNode.TailStatus.TAIL_DIRECT);
if (actionRes instanceof Atom resAtom) {
var isTrue = resAtom.getConstructor() == builtins.bool().getTrue();
if (isTrue) {
return ctx.getNothing();
} else {
throw new PanicException(builtins.error().makeAssertionError(msg), this);
}
} else {
resultIsNotAtomProfile.enter();
return checkResultSlowPath(actionRes, msg);
}
}

@TruffleBoundary
private Object checkResultSlowPath(Object actionRes, Text msg) {
var ctx = EnsoContext.get(this);
var builtins = ctx.getBuiltins();
try {
if (InteropLibrary.getUncached().asBoolean(actionRes)) {
return ctx.getNothing();
} else {
throw new PanicException(builtins.error().makeAssertionError(msg), this);
}
} catch (UnsupportedMessageException e) {
var typeError =
builtins
.error()
.makeTypeError(
builtins.bool().getType(),
TypeOfNode.getUncached().execute(actionRes),
"Result of assert action");
throw new PanicException(typeError, this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
Expand All @@ -15,6 +16,12 @@
description = "Gets the current execution stacktrace.",
autoRegister = false)
public class GetStackTraceNode extends Node {

@NeverDefault
public static GetStackTraceNode create() {
return new GetStackTraceNode();
}

EnsoObject execute(VirtualFrame requestOwnStackFrame) {
var exception = new PanicException("Stacktrace", this);
TruffleStackTrace.fillIn(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.OptionsHelper;
import org.enso.interpreter.instrument.NotificationHandler;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.scope.TopLevelScope;
Expand Down Expand Up @@ -78,6 +77,7 @@ public final class EnsoContext {

private final EnsoLanguage language;
private final Env environment;
private final boolean assertionsEnabled;
private @CompilationFinal Compiler compiler;
private final PrintStream out;
private final PrintStream err;
Expand Down Expand Up @@ -136,7 +136,7 @@ public EnsoContext(
this.isIrCachingDisabled =
getOption(RuntimeOptions.DISABLE_IR_CACHES_KEY) || isParallelismEnabled;
this.executionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT);

this.assertionsEnabled = shouldAssertionsBeEnabled();
this.shouldWaitForPendingSerializationJobs =
getOption(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS_KEY);
this.compilerConfig =
Expand Down Expand Up @@ -252,6 +252,19 @@ public void shutdown() {
compiler.shutdown(shouldWaitForPendingSerializationJobs);
}

private boolean shouldAssertionsBeEnabled() {
var envVar = environment.getEnvironment().get("ENSO_ENABLE_ASSERTIONS");
if (envVar != null) {
return Boolean.parseBoolean(envVar);
}
return isJvmAssertionsEnabled();
}

private static boolean isJvmAssertionsEnabled() {
boolean assertionsEnabled = false;
assert assertionsEnabled = true;
return assertionsEnabled;
}
/**
* Creates a truffle file for a given standard file.
*
Expand Down Expand Up @@ -609,6 +622,10 @@ public boolean isUseGlobalCache() {
return getOption(RuntimeOptions.USE_GLOBAL_IR_CACHE_LOCATION_KEY);
}

public boolean isAssertionsEnabled() {
return assertionsEnabled;
}

/**
* Checks whether we are running in interactive mode.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.oracle.truffle.api.CompilerDirectives;
import org.enso.interpreter.node.expression.builtin.error.ArithmeticError;
import org.enso.interpreter.node.expression.builtin.error.ArityError;
import org.enso.interpreter.node.expression.builtin.error.AssertionError;
import org.enso.interpreter.node.expression.builtin.error.CaughtPanic;
import org.enso.interpreter.node.expression.builtin.error.CompileError;
import org.enso.interpreter.node.expression.builtin.error.ForbiddenOperation;
Expand Down Expand Up @@ -41,6 +42,7 @@ public final class Error {
private final SyntaxError syntaxError;
private final TypeError typeError;
private final CompileError compileError;
private final AssertionError assertionError;
private final IndexOutOfBounds indexOutOfBounds;
private final InexhaustivePatternMatch inexhaustivePatternMatch;
private final UninitializedState uninitializedState;
Expand Down Expand Up @@ -77,6 +79,7 @@ public Error(Builtins builtins, EnsoContext context) {
syntaxError = builtins.getBuiltinType(SyntaxError.class);
typeError = builtins.getBuiltinType(TypeError.class);
compileError = builtins.getBuiltinType(CompileError.class);
assertionError = builtins.getBuiltinType(AssertionError.class);
indexOutOfBounds = builtins.getBuiltinType(IndexOutOfBounds.class);
inexhaustivePatternMatch = builtins.getBuiltinType(InexhaustivePatternMatch.class);
uninitializedState = builtins.getBuiltinType(UninitializedState.class);
Expand Down Expand Up @@ -108,6 +111,10 @@ public Atom makeCompileError(Object message) {
return compileError.newInstance(message);
}

public Atom makeAssertionError(Text text) {
return assertionError.newInstance(text);
}

public Atom makeIndexOutOfBounds(long index, long length) {
return indexOutOfBounds.newInstance(index, length);
}
Expand Down
Loading

0 comments on commit 9f15b90

Please sign in to comment.