mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
Compile Error Handling (#687)
This commit is contained in:
parent
92d8393495
commit
4ba26a3034
4
.github/workflows/scala.yml
vendored
4
.github/workflows/scala.yml
vendored
@ -40,7 +40,7 @@ jobs:
|
||||
- name: Setup conda
|
||||
uses: s-weigand/setup-conda@v1
|
||||
with:
|
||||
update-conda: true
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- name: Setup Conda Environment on Windows
|
||||
if: runner.os == 'Windows'
|
||||
@ -51,7 +51,7 @@ jobs:
|
||||
if: runner.os == 'Windows'
|
||||
run: conda activate enso
|
||||
- name: Install FlatBuffers Compiler
|
||||
run: conda install flatbuffers=1.12.0
|
||||
run: conda install --freeze-installed flatbuffers=1.12.0
|
||||
- name: Setup GraalVM Environment
|
||||
uses: DeLaGuardo/setup-graalvm@2.0
|
||||
with:
|
||||
|
@ -108,7 +108,7 @@ class MainModule(serverConfig: LanguageServerConfig) {
|
||||
.allowAllAccess(true)
|
||||
.allowExperimentalOptions(true)
|
||||
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
|
||||
.option(RuntimeOptions.getPackagesPathOption, serverConfig.contentRootPath)
|
||||
.option(RuntimeOptions.PACKAGES_PATH, serverConfig.contentRootPath)
|
||||
.serverTransport((uri: URI, peerEndpoint: MessageEndpoint) => {
|
||||
if (uri.toString == RuntimeServerInfo.URI) {
|
||||
val connection = new RuntimeConnector.Endpoint(
|
||||
|
@ -1,20 +1,25 @@
|
||||
package org.enso.polyglot;
|
||||
|
||||
import org.enso.polyglot.LanguageInfo;
|
||||
import org.graalvm.options.OptionDescriptor;
|
||||
import org.graalvm.options.OptionDescriptors;
|
||||
import org.graalvm.options.OptionKey;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Class representing runtime options supported by the Enso engine. */
|
||||
public class RuntimeOptions {
|
||||
private static final String PACKAGES_PATH = optionName("packagesPath");
|
||||
public static final String PACKAGES_PATH = optionName("packagesPath");
|
||||
public static final OptionKey<String> PACKAGES_PATH_KEY = new OptionKey<>("");
|
||||
private static final OptionDescriptor PACKAGES_PATH_DESCRIPTOR =
|
||||
OptionDescriptor.newBuilder(PACKAGES_PATH_KEY, getPackagesPathOption()).build();
|
||||
OptionDescriptor.newBuilder(PACKAGES_PATH_KEY, PACKAGES_PATH).build();
|
||||
|
||||
public static final String STRICT_ERRORS = optionName("strictErrors");
|
||||
public static final OptionKey<Boolean> STRICT_ERRORS_KEY = new OptionKey<>(false);
|
||||
private static final OptionDescriptor STRICT_ERRORS_DESCRIPTOR =
|
||||
OptionDescriptor.newBuilder(STRICT_ERRORS_KEY, STRICT_ERRORS).build();
|
||||
|
||||
public static final OptionDescriptors OPTION_DESCRIPTORS =
|
||||
OptionDescriptors.create(Collections.singletonList(PACKAGES_PATH_DESCRIPTOR));
|
||||
OptionDescriptors.create(Arrays.asList(PACKAGES_PATH_DESCRIPTOR, STRICT_ERRORS_DESCRIPTOR));
|
||||
|
||||
/**
|
||||
* Canonicalizes the option name by prefixing it with the language name.
|
||||
@ -25,16 +30,4 @@ public class RuntimeOptions {
|
||||
private static String optionName(String name) {
|
||||
return LanguageInfo.ID + "." + name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An option to pass a $PATH-like list of locations of all known modules that can be imported in
|
||||
* the current run.
|
||||
*
|
||||
* @return the name of this option
|
||||
*/
|
||||
public static String getPackagesPathOption() {
|
||||
return PACKAGES_PATH;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class ModuleManagementTest extends AnyFlatSpec with Matchers {
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.getPackagesPathOption, pkg.root.getAbsolutePath)
|
||||
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
@ -25,13 +25,15 @@ class ContextFactory {
|
||||
packagesPath: String = "",
|
||||
in: InputStream,
|
||||
out: OutputStream,
|
||||
repl: Repl
|
||||
repl: Repl,
|
||||
strictErrors: Boolean = false
|
||||
): PolyglotContext = {
|
||||
val context = Context
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.getPackagesPathOption, packagesPath)
|
||||
.option(RuntimeOptions.PACKAGES_PATH, packagesPath)
|
||||
.option(RuntimeOptions.STRICT_ERRORS, strictErrors.toString)
|
||||
.out(out)
|
||||
.in(in)
|
||||
.build
|
||||
|
@ -10,7 +10,7 @@ import org.enso.languageserver.boot
|
||||
import org.enso.languageserver.boot.LanguageServerConfig
|
||||
import org.enso.pkg.Package
|
||||
import org.enso.polyglot.{LanguageInfo, Module, PolyglotContext}
|
||||
import org.graalvm.polyglot.Value
|
||||
import org.graalvm.polyglot.{PolyglotException, Value}
|
||||
|
||||
import scala.annotation.unused
|
||||
import scala.util.Try
|
||||
@ -172,7 +172,8 @@ object Main {
|
||||
packagePath,
|
||||
System.in,
|
||||
System.out,
|
||||
Repl(TerminalIO())
|
||||
Repl(TerminalIO()),
|
||||
strictErrors = true
|
||||
)
|
||||
if (projectMode) {
|
||||
val pkg = Package.fromDirectory(file)
|
||||
@ -196,18 +197,25 @@ object Main {
|
||||
): Unit = {
|
||||
val topScope = context.getTopScope
|
||||
val mainModule = topScope.getModule(mainModuleName)
|
||||
runMain(mainModule): Unit
|
||||
runMain(mainModule)
|
||||
}
|
||||
|
||||
private def runSingleFile(context: PolyglotContext, file: File): Unit = {
|
||||
val mainModule = context.evalModule(file)
|
||||
runMain(mainModule): Unit
|
||||
runMain(mainModule)
|
||||
}
|
||||
|
||||
private def runMain(mainModule: Module): Value = {
|
||||
val mainCons = mainModule.getAssociatedConstructor
|
||||
val mainFun = mainModule.getMethod(mainCons, "main")
|
||||
mainFun.execute(mainCons.newInstance())
|
||||
try {
|
||||
val mainCons = mainModule.getAssociatedConstructor
|
||||
val mainFun = mainModule.getMethod(mainCons, "main")
|
||||
mainFun.execute(mainCons.newInstance())
|
||||
} catch {
|
||||
case e: PolyglotException =>
|
||||
System.err.println(e.getMessage)
|
||||
exitFail()
|
||||
throw new RuntimeException("Impossible to reach here.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,35 @@
|
||||
package org.enso.interpreter.node.expression.constant;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
|
||||
/** Throws a runtime panic containing a statically-known payload. */
|
||||
public class ErrorNode extends ExpressionNode {
|
||||
private final Object payload;
|
||||
|
||||
private ErrorNode(Object payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the node, by throwing a new panic containing the specified payload.
|
||||
*
|
||||
* @param frame the stack frame for execution
|
||||
* @return never returns
|
||||
*/
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
throw new PanicException(payload, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of this node.
|
||||
*
|
||||
* @param payload the payload carried by exceptions thrown in the course of this node's execution.
|
||||
* @return a new instance of this node.
|
||||
*/
|
||||
public static ErrorNode build(Object payload) {
|
||||
return new ErrorNode(payload);
|
||||
}
|
||||
}
|
@ -44,6 +44,8 @@ public class Builtins {
|
||||
private final AtomConstructor function;
|
||||
private final AtomConstructor text;
|
||||
private final AtomConstructor debug;
|
||||
private final AtomConstructor syntaxError;
|
||||
private final AtomConstructor compileError;
|
||||
|
||||
/**
|
||||
* Creates an instance with builtin methods installed.
|
||||
@ -59,6 +61,14 @@ public class Builtins {
|
||||
function = new AtomConstructor("Function", scope).initializeFields();
|
||||
text = new AtomConstructor("Text", scope).initializeFields();
|
||||
debug = new AtomConstructor("Debug", scope).initializeFields();
|
||||
syntaxError =
|
||||
new AtomConstructor("Syntax_Error", scope)
|
||||
.initializeFields(
|
||||
new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE));
|
||||
compileError =
|
||||
new AtomConstructor("Compile_Error", scope)
|
||||
.initializeFields(
|
||||
new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE));
|
||||
|
||||
AtomConstructor nil = new AtomConstructor("Nil", scope).initializeFields();
|
||||
AtomConstructor cons =
|
||||
@ -85,6 +95,9 @@ public class Builtins {
|
||||
scope.registerConstructor(state);
|
||||
scope.registerConstructor(debug);
|
||||
|
||||
scope.registerConstructor(syntaxError);
|
||||
scope.registerConstructor(compileError);
|
||||
|
||||
scope.registerMethod(io, "println", PrintNode.makeFunction(language));
|
||||
|
||||
scope.registerMethod(panic, "throw", PanicNode.makeFunction(language));
|
||||
@ -166,6 +179,16 @@ public class Builtins {
|
||||
return debug;
|
||||
}
|
||||
|
||||
/** @return the builtin {@code Syntax_Error} atom constructor. */
|
||||
public AtomConstructor syntaxError() {
|
||||
return syntaxError;
|
||||
}
|
||||
|
||||
/** @return the builtin {@code Compile_Error} atom constructor. */
|
||||
public AtomConstructor compileError() {
|
||||
return compileError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the builtin module scope.
|
||||
*
|
||||
|
@ -18,6 +18,7 @@ import org.enso.interpreter.runtime.scope.TopLevelScope;
|
||||
import org.enso.interpreter.util.ScalaConversions;
|
||||
import org.enso.pkg.Package;
|
||||
import org.enso.pkg.QualifiedName;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
|
||||
/**
|
||||
* The language context is the internal state of the language that is associated with each thread in
|
||||
@ -194,4 +195,13 @@ public class Context {
|
||||
public AtomConstructor getUnit() {
|
||||
return getBuiltins().unit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the strict errors option was set for this context.
|
||||
*
|
||||
* @return true if the strict errors option is enabled, false otherwise.
|
||||
*/
|
||||
public boolean isStrictErrors() {
|
||||
return getEnvironment().getOptions().get(RuntimeOptions.STRICT_ERRORS_KEY);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ public class PanicException extends RuntimeException implements TruffleException
|
||||
* @param location the node throwing this exception, for use in guest stack traces
|
||||
*/
|
||||
public PanicException(Object payload, Node location) {
|
||||
super(payload.toString());
|
||||
this.payload = payload;
|
||||
this.location = location;
|
||||
}
|
||||
|
@ -7,9 +7,11 @@ import com.oracle.truffle.api.source.Source
|
||||
import org.enso.compiler.codegen.{AstToIR, IRToTruffle}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.{Expression, Module}
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.exception.{CompilationAbortedException, CompilerError}
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
import org.enso.compiler.pass.analyse._
|
||||
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
@ -68,6 +70,7 @@ class Compiler(
|
||||
val parsedAST = parse(source)
|
||||
val expr = generateIR(parsedAST)
|
||||
val compilerOutput = runCompilerPhases(expr)
|
||||
runErrorHandling(compilerOutput, source)
|
||||
truffleCodegen(compilerOutput, source, scope)
|
||||
}
|
||||
|
||||
@ -132,6 +135,7 @@ class Compiler(
|
||||
|
||||
generateIRInline(parsed).flatMap { ir =>
|
||||
val compilerOutput = runCompilerPhasesInline(ir, inlineContext)
|
||||
runErrorHandlingInline(compilerOutput, source, inlineContext)
|
||||
Some(truffleCodegenInline(compilerOutput, source, inlineContext))
|
||||
}
|
||||
}
|
||||
@ -210,6 +214,75 @@ class Compiler(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the strict error handling mechanism (if enabled in the language
|
||||
* context) for the inline compiler flow.
|
||||
*
|
||||
* @param ir the IR after compilation passes.
|
||||
* @param source the original source code.
|
||||
* @param inlineContext the inline compilation context.
|
||||
*/
|
||||
def runErrorHandlingInline(
|
||||
ir: IR.Expression,
|
||||
source: Source,
|
||||
inlineContext: InlineContext
|
||||
): Unit = if (context.isStrictErrors) {
|
||||
val errors = GatherErrors
|
||||
.runExpression(ir, inlineContext)
|
||||
.unsafeGetMetadata[GatherErrors.Errors](
|
||||
"No errors metadata right after the gathering pass."
|
||||
)
|
||||
.errors
|
||||
reportErrors(errors, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the strict error handling mechanism (if enabled in the language
|
||||
* context) for the module-level compiler flow.
|
||||
*
|
||||
* @param ir the IR after compilation passes.
|
||||
* @param source the original source code.
|
||||
*/
|
||||
def runErrorHandling(ir: IR.Module, source: Source): Unit =
|
||||
if (context.isStrictErrors) {
|
||||
val errors = GatherErrors
|
||||
.runModule(ir)
|
||||
.unsafeGetMetadata[GatherErrors.Errors](
|
||||
"No errors metadata right after the gathering pass."
|
||||
)
|
||||
.errors
|
||||
reportErrors(errors, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports compilation errors to the standard output and throws an exception
|
||||
* breaking the execution flow.
|
||||
*
|
||||
* @param errors all the errors found in the program IR.
|
||||
* @param source the original source code.
|
||||
*/
|
||||
def reportErrors(errors: List[IR.Error], source: Source): Unit =
|
||||
if (errors.nonEmpty) {
|
||||
context.getOut.println("Compiler encountered errors:")
|
||||
errors.foreach { err =>
|
||||
val srcLocation = err.location
|
||||
.map { loc =>
|
||||
val section = source
|
||||
.createSection(loc.location.start, loc.location.length)
|
||||
val locStr =
|
||||
"" + section.getStartLine + ":" +
|
||||
section.getStartColumn + "-" +
|
||||
section.getEndLine + ":" +
|
||||
section.getEndColumn
|
||||
"[" + locStr + "]"
|
||||
}
|
||||
.getOrElse("")
|
||||
val fullMsg = source.getName + srcLocation + ": " + err.message
|
||||
context.getOut.println(fullMsg)
|
||||
}
|
||||
throw new CompilationAbortedException
|
||||
}
|
||||
|
||||
/** Generates code for the truffle interpreter.
|
||||
*
|
||||
* @param ir the program to translate
|
||||
|
@ -31,9 +31,8 @@ object AstToIR {
|
||||
def translate(inputAST: AST): Module = {
|
||||
inputAST match {
|
||||
case AST.Module.any(inputAST) => translateModule(inputAST)
|
||||
case _ => {
|
||||
case _ =>
|
||||
throw new UnhandledEntity(inputAST, "translate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +110,7 @@ object AstToIR {
|
||||
inputAST match {
|
||||
case AST.Def(consName, args, body) =>
|
||||
if (body.isDefined) {
|
||||
throw new RuntimeException("Cannot support complex type defs yet!!!!")
|
||||
throw new UnhandledEntity(inputAST, "translateModuleSymbol")
|
||||
} else {
|
||||
Module.Scope.Definition
|
||||
.Atom(
|
||||
@ -206,9 +205,8 @@ object AstToIR {
|
||||
case AST.App.any(inputAST) => translateApplicationLike(inputAST)
|
||||
case AST.Mixfix.any(inputAST) => translateApplicationLike(inputAST)
|
||||
case AST.Literal.any(inputAST) => translateLiteral(inputAST)
|
||||
case AST.Group.any(inputAST) =>
|
||||
translateGroup(inputAST, translateExpression)
|
||||
case AST.Ident.any(inputAST) => translateIdent(inputAST)
|
||||
case AST.Group.any(inputAST) => translateGroup(inputAST)
|
||||
case AST.Ident.any(inputAST) => translateIdent(inputAST)
|
||||
case AstView.Block(lines, retLine) =>
|
||||
Expression.Block(
|
||||
lines.map(translateExpression),
|
||||
@ -218,8 +216,9 @@ object AstToIR {
|
||||
case AST.Comment.any(inputAST) => translateComment(inputAST)
|
||||
case AST.Invalid.any(inputAST) => translateInvalid(inputAST)
|
||||
case AST.Foreign(_, _, _) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support foreign language blocks"
|
||||
Error.Syntax(
|
||||
inputAST,
|
||||
Error.Syntax.UnsupportedSyntax("foreign blocks")
|
||||
)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(inputAST, "translateExpression")
|
||||
@ -246,10 +245,13 @@ object AstToIR {
|
||||
literal match {
|
||||
case AST.Literal.Number(base, number) => {
|
||||
if (base.isDefined && base.get != "10") {
|
||||
throw new RuntimeException("Only base 10 is currently supported")
|
||||
Error.Syntax(
|
||||
literal,
|
||||
Error.Syntax.UnsupportedSyntax("non-base-10 number literals")
|
||||
)
|
||||
} else {
|
||||
Literal.Number(number, getIdentifiedLocation(literal))
|
||||
}
|
||||
|
||||
Literal.Number(number, getIdentifiedLocation(literal))
|
||||
}
|
||||
case AST.Literal.Text.any(literal) =>
|
||||
literal.shape match {
|
||||
@ -272,9 +274,15 @@ object AstToIR {
|
||||
|
||||
Literal.Text(fullString, getIdentifiedLocation(literal))
|
||||
case AST.Literal.Text.Block.Fmt(_, _, _) =>
|
||||
throw new RuntimeException("Format strings not yet supported")
|
||||
Error.Syntax(
|
||||
literal,
|
||||
Error.Syntax.UnsupportedSyntax("format strings")
|
||||
)
|
||||
case AST.Literal.Text.Line.Fmt(_) =>
|
||||
throw new RuntimeException("Format strings not yet supported")
|
||||
Error.Syntax(
|
||||
literal,
|
||||
Error.Syntax.UnsupportedSyntax("format strings")
|
||||
)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(literal.shape, "translateLiteral")
|
||||
}
|
||||
@ -399,24 +407,20 @@ object AstToIR {
|
||||
getIdentifiedLocation(callable)
|
||||
)
|
||||
case AST.App.Prefix(_, _) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not support arbitrary prefix expressions"
|
||||
)
|
||||
throw new UnhandledEntity(callable, "translateCallable")
|
||||
case AST.App.Section.any(_) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support operator sections"
|
||||
Error.Syntax(
|
||||
callable,
|
||||
Error.Syntax.UnsupportedSyntax("operator sections")
|
||||
)
|
||||
case AST.Mixfix(nameSegments, args) =>
|
||||
val realNameSegments = nameSegments.collect {
|
||||
case AST.Ident.Var.any(v) => v
|
||||
}
|
||||
|
||||
if (realNameSegments.length != nameSegments.length) {
|
||||
throw new RuntimeException("Badly named mixfix function.")
|
||||
case AST.Ident.Var.any(v) => v.name
|
||||
case AST.Ident.Cons.any(v) => v.name.toLowerCase
|
||||
}
|
||||
|
||||
val functionName =
|
||||
AST.Ident.Var(realNameSegments.map(_.name).mkString("_"))
|
||||
AST.Ident.Var(realNameSegments.mkString("_"))
|
||||
|
||||
Application.Prefix(
|
||||
translateExpression(functionName),
|
||||
@ -447,12 +451,19 @@ object AstToIR {
|
||||
case AST.Ident.Cons(name) =>
|
||||
Name.Literal(name, getIdentifiedLocation(identifier))
|
||||
case AST.Ident.Blank(_) =>
|
||||
throw new RuntimeException("Blanks not yet properly supported")
|
||||
Error.Syntax(
|
||||
identifier,
|
||||
Error.Syntax.UnsupportedSyntax("blanks")
|
||||
)
|
||||
case AST.Ident.Opr.any(_) =>
|
||||
throw new RuntimeException("Operators not generically supported yet")
|
||||
Error.Syntax(
|
||||
identifier,
|
||||
Error.Syntax.UnsupportedSyntax("operator sections")
|
||||
)
|
||||
case AST.Ident.Mod(_) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not support arbitrary module identifiers yet"
|
||||
Error.Syntax(
|
||||
identifier,
|
||||
Error.Syntax.UnsupportedSyntax("module identifiers")
|
||||
)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(identifier, "translateIdent")
|
||||
@ -533,17 +544,12 @@ object AstToIR {
|
||||
* It is currently an error to have an empty group.
|
||||
*
|
||||
* @param group the group to translate
|
||||
* @param translator the function to apply to the group's contents
|
||||
* @tparam T the result type of translating the expression contained in
|
||||
* `group`
|
||||
* @return the [[Core]] representation of the contents of `group`
|
||||
*/
|
||||
def translateGroup[T](group: AST.Group, translator: AST => T): T = {
|
||||
def translateGroup(group: AST.Group): Expression = {
|
||||
group.body match {
|
||||
case Some(ast) => translator(ast)
|
||||
case None => {
|
||||
throw new RuntimeException("Empty group")
|
||||
}
|
||||
case Some(ast) => translateExpression(ast)
|
||||
case None => Error.Syntax(group, Error.Syntax.EmptyParentheses)
|
||||
}
|
||||
}
|
||||
|
||||
@ -569,25 +575,27 @@ object AstToIR {
|
||||
def translateInvalid(invalid: AST.Invalid): Expression = {
|
||||
invalid match {
|
||||
case AST.Invalid.Unexpected(_, _) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support unexpected blocks properly"
|
||||
Error.Syntax(
|
||||
invalid,
|
||||
Error.Syntax.UnexpectedExpression
|
||||
)
|
||||
case AST.Invalid.Unrecognized(_) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support unrecognised tokens properly"
|
||||
Error.Syntax(
|
||||
invalid,
|
||||
Error.Syntax.UnrecognizedToken
|
||||
)
|
||||
case AST.Ident.InvalidSuffix(_, _) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support invalid suffixes properly"
|
||||
Error.Syntax(
|
||||
invalid,
|
||||
Error.Syntax.InvalidSuffix
|
||||
)
|
||||
case AST.Literal.Text.Unclosed(_) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support unclosed text literals properly"
|
||||
Error.Syntax(
|
||||
invalid,
|
||||
Error.Syntax.UnclosedTextLiteral
|
||||
)
|
||||
case _ =>
|
||||
throw new RuntimeException(
|
||||
"Fatal: Unhandled entity in processInvalid = " + invalid
|
||||
)
|
||||
throw new UnhandledEntity(invalid, "translateInvalid")
|
||||
}
|
||||
}
|
||||
|
||||
@ -603,8 +611,9 @@ object AstToIR {
|
||||
def translateComment(comment: AST): Expression = {
|
||||
comment match {
|
||||
case AST.Comment(_) =>
|
||||
throw new RuntimeException(
|
||||
"Enso does not yet support comments properly"
|
||||
Error.Syntax(
|
||||
comment,
|
||||
Error.Syntax.UnsupportedSyntax("comments")
|
||||
)
|
||||
case AST.Documented(doc, _, ast) =>
|
||||
Comment.Documentation(
|
||||
|
@ -3,7 +3,7 @@ package org.enso.compiler.codegen
|
||||
import com.oracle.truffle.api.Truffle
|
||||
import com.oracle.truffle.api.source.{Source, SourceSection}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.IdentifiedLocation
|
||||
import org.enso.compiler.core.IR.{Error, IdentifiedLocation}
|
||||
import org.enso.compiler.exception.{CompilerError, UnhandledEntity}
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Scope => AliasScope}
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph => AliasGraph}
|
||||
@ -23,7 +23,8 @@ import org.enso.interpreter.node.callable.{ApplicationNode, InvokeCallableNode}
|
||||
import org.enso.interpreter.node.controlflow._
|
||||
import org.enso.interpreter.node.expression.constant.{
|
||||
ConstructorNode,
|
||||
DynamicSymbolNode
|
||||
DynamicSymbolNode,
|
||||
ErrorNode
|
||||
}
|
||||
import org.enso.interpreter.node.expression.literal.{
|
||||
IntegerLiteralNode,
|
||||
@ -338,11 +339,7 @@ class IRToTruffle(
|
||||
case binding: IR.Expression.Binding => processBinding(binding)
|
||||
case caseExpr: IR.Case => processCase(caseExpr)
|
||||
case comment: IR.Comment => processComment(comment)
|
||||
case err: IR.Error =>
|
||||
throw new CompilerError(
|
||||
s"No errors should remain by the point of truffle codegen, but " +
|
||||
s"found $err."
|
||||
)
|
||||
case err: IR.Error => processError(err)
|
||||
case IR.Foreign.Definition(_, _, _, _) =>
|
||||
throw new CompilerError(
|
||||
s"Foreign expressions not yet implemented: $ir."
|
||||
@ -555,6 +552,34 @@ class IRToTruffle(
|
||||
setLocation(TextLiteralNode.build(text), location)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a runtime implementation for compile error nodes.
|
||||
*
|
||||
* @param error the IR representing a compile error.
|
||||
* @return a runtime node representing the error.
|
||||
*/
|
||||
def processError(error: IR.Error): RuntimeExpression = {
|
||||
val payload: AnyRef = error match {
|
||||
case IR.Empty(_, _) =>
|
||||
throw new CompilerError("Unexpected Empty IR during codegen.")
|
||||
case Error.InvalidIR(_, _) =>
|
||||
throw new CompilerError("Unexpected Invalid IR during codegen.")
|
||||
case err: Error.Syntax =>
|
||||
context.getBuiltins
|
||||
.syntaxError()
|
||||
.newInstance(err.message)
|
||||
case err: Error.Redefined.Binding =>
|
||||
context.getBuiltins
|
||||
.compileError()
|
||||
.newInstance(err.message)
|
||||
case err: Error.Redefined.Argument =>
|
||||
context.getBuiltins
|
||||
.compileError()
|
||||
.newInstance(err.message)
|
||||
}
|
||||
setLocation(ErrorNode.build(payload), error.location)
|
||||
}
|
||||
|
||||
/** Generates code for an Enso function body.
|
||||
*
|
||||
* @param arguments the arguments to the function
|
||||
|
@ -73,6 +73,18 @@ sealed trait IR {
|
||||
*/
|
||||
def mapExpressions(fn: Expression => Expression): IR
|
||||
|
||||
/** Gets the list of all children IR nodes of this node.
|
||||
*
|
||||
* @return this node's children.
|
||||
*/
|
||||
def children: List[IR]
|
||||
|
||||
/** Lists all the nodes in the preorder walk of the tree of this node.
|
||||
*
|
||||
* @return all the descendants of this node.
|
||||
*/
|
||||
def preorder: List[IR] = this :: children.flatMap(_.preorder)
|
||||
|
||||
/** Pretty prints the IR.
|
||||
*
|
||||
* @return a pretty-printed representation of the IR
|
||||
@ -183,6 +195,11 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
|
||||
override def message: String =
|
||||
"Empty IR: Please report this as a compiler bug."
|
||||
}
|
||||
|
||||
// === Module ===============================================================
|
||||
@ -238,6 +255,8 @@ object IR {
|
||||
)
|
||||
}
|
||||
|
||||
override def children: List[IR] = imports ++ bindings
|
||||
|
||||
def transformExpressions(
|
||||
fn: PartialFunction[Expression, Expression]
|
||||
): Module = {
|
||||
@ -316,6 +335,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
|
||||
/** A representation of top-level definitions. */
|
||||
@ -383,6 +404,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = name :: arguments
|
||||
}
|
||||
|
||||
/** The definition of a method for a given constructor [[typeName]].
|
||||
@ -453,6 +476,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(typeName, methodName, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -547,6 +572,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = expressions :+ returnValue
|
||||
}
|
||||
|
||||
/** A binding expression of the form `name = expr`
|
||||
@ -604,6 +631,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(name, expression)
|
||||
}
|
||||
}
|
||||
|
||||
@ -662,6 +691,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
|
||||
/** A textual Enso literal.
|
||||
@ -711,6 +742,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
}
|
||||
|
||||
@ -772,6 +805,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
|
||||
/** A representation of the name `this`, used to refer to the current type.
|
||||
@ -817,6 +852,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
|
||||
/** A representation of the name `here`, used to refer to the current
|
||||
@ -862,6 +899,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
}
|
||||
|
||||
@ -933,6 +972,8 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
|
||||
override def children: List[IR] = List(typed, signature)
|
||||
}
|
||||
object Ascription extends Info {
|
||||
override val name: String = ":"
|
||||
@ -993,6 +1034,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(typed, context)
|
||||
|
||||
}
|
||||
object Context extends Info {
|
||||
override val name: String = "in"
|
||||
@ -1069,6 +1113,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(label, memberType, value)
|
||||
|
||||
}
|
||||
object Member extends Info {
|
||||
override val name: String = "_ : _ = _"
|
||||
@ -1130,6 +1177,9 @@ object IR {
|
||||
|passData = ${this.showPassData},
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, right)
|
||||
|
||||
}
|
||||
object Subsumption extends Info {
|
||||
override val name: String = "<:"
|
||||
@ -1189,6 +1239,9 @@ object IR {
|
||||
|passData = ${this.showPassData},
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, right)
|
||||
|
||||
}
|
||||
object Equality extends Info {
|
||||
override val name: String = "~"
|
||||
@ -1248,6 +1301,9 @@ object IR {
|
||||
|passData = ${this.showPassData},
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, right)
|
||||
|
||||
}
|
||||
object Concat extends Info {
|
||||
override val name: String = ","
|
||||
@ -1307,6 +1363,9 @@ object IR {
|
||||
|passData = ${this.showPassData},
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, right)
|
||||
|
||||
}
|
||||
object Union extends Info {
|
||||
override val name: String = "|"
|
||||
@ -1368,6 +1427,9 @@ object IR {
|
||||
|passData = ${this.showPassData},
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, right)
|
||||
|
||||
}
|
||||
object Intersection extends Info {
|
||||
override val name: String = "&"
|
||||
@ -1429,6 +1491,9 @@ object IR {
|
||||
|passData = ${this.showPassData},
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, right)
|
||||
|
||||
}
|
||||
object Subtraction extends Info {
|
||||
override val name: String = "\\"
|
||||
@ -1527,6 +1592,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = arguments :+ body
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1608,6 +1676,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = name :: defaultValue.toList
|
||||
|
||||
}
|
||||
|
||||
// TODO [AA] Add support for `_` ignored arguments.
|
||||
@ -1685,6 +1756,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = function :: arguments
|
||||
|
||||
}
|
||||
|
||||
/** A representation of a term that is explicitly forced.
|
||||
@ -1737,6 +1811,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(target)
|
||||
|
||||
}
|
||||
|
||||
/** Operator applications in Enso. */
|
||||
@ -1806,6 +1883,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(left, operator, right)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1896,6 +1976,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = name.toList :+ value
|
||||
|
||||
}
|
||||
|
||||
// TODO [AA] Add support for the `_` lambda shorthand argument (can be
|
||||
@ -1975,6 +2058,10 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] =
|
||||
scrutinee :: branches.toList ++ fallback.toList
|
||||
|
||||
}
|
||||
|
||||
/** A branch in a case statement.
|
||||
@ -2032,6 +2119,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(pattern, expression)
|
||||
|
||||
}
|
||||
|
||||
/** The different types of patterns that can occur in a match. */
|
||||
@ -2113,6 +2203,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(commented)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2179,6 +2272,9 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2187,7 +2283,13 @@ object IR {
|
||||
/** A trait for all errors in Enso's IR. */
|
||||
sealed trait Error extends Expression {
|
||||
override def mapExpressions(fn: Expression => Expression): Error
|
||||
override def addMetadata(newData: Metadata): Error
|
||||
|
||||
override def addMetadata(newData: Metadata): Error
|
||||
|
||||
/**
|
||||
* @return a human-readable description of this error condition.
|
||||
*/
|
||||
def message: String
|
||||
}
|
||||
object Error {
|
||||
|
||||
@ -2209,13 +2311,15 @@ object IR {
|
||||
/** A representation of an Enso syntax error.
|
||||
*
|
||||
* @param ast the erroneous AST
|
||||
* @param reason the cause of this error
|
||||
* @param passData the pass metadata associated with this node
|
||||
*/
|
||||
sealed case class Syntax(
|
||||
ast: AST,
|
||||
reason: Syntax.Reason,
|
||||
override val passData: ISet[Metadata] = ISet()
|
||||
) extends Error
|
||||
with Kind.Static
|
||||
with Kind.Interactive
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
@ -2228,10 +2332,11 @@ object IR {
|
||||
*/
|
||||
def copy(
|
||||
ast: AST = ast,
|
||||
reason: Syntax.Reason = reason,
|
||||
passData: ISet[Metadata] = passData,
|
||||
id: Identifier = id
|
||||
): Syntax = {
|
||||
val res = Syntax(ast, passData)
|
||||
val res = Syntax(ast, reason, passData)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
@ -2254,6 +2359,50 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List()
|
||||
|
||||
override def message: String = reason.explanation
|
||||
}
|
||||
|
||||
object Syntax {
|
||||
|
||||
/**
|
||||
* A common type for all syntax errors expected by the language.
|
||||
*/
|
||||
sealed trait Reason {
|
||||
|
||||
/**
|
||||
* @return a human-readable description of the error.
|
||||
*/
|
||||
def explanation: String
|
||||
}
|
||||
|
||||
case class UnsupportedSyntax(syntaxName: String) extends Reason {
|
||||
override def explanation: String =
|
||||
s"Syntax is not supported yet: $syntaxName."
|
||||
}
|
||||
|
||||
case object EmptyParentheses extends Reason {
|
||||
override def explanation: String = "Parentheses can't be empty."
|
||||
}
|
||||
|
||||
case object UnexpectedExpression extends Reason {
|
||||
override def explanation: String = "Unexpected expression."
|
||||
}
|
||||
|
||||
case object UnrecognizedToken extends Reason {
|
||||
override def explanation: String = "Unrecognized token."
|
||||
}
|
||||
|
||||
case object InvalidSuffix extends Reason {
|
||||
override def explanation: String = "Invalid suffix."
|
||||
}
|
||||
|
||||
case object UnclosedTextLiteral extends Reason {
|
||||
override def explanation: String = "Unclosed text literal."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A representation of an invalid piece of IR.
|
||||
@ -2304,6 +2453,12 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(ir)
|
||||
|
||||
override def message: String =
|
||||
"InvalidIR: Please report this as a compiler bug."
|
||||
|
||||
}
|
||||
|
||||
/** Errors pertaining to the redefinition of language constructs that are
|
||||
@ -2318,7 +2473,7 @@ object IR {
|
||||
* @param passData the pass metadata for the error
|
||||
*/
|
||||
sealed case class Argument(
|
||||
invalidArgDef: IR.DefinitionArgument,
|
||||
invalidArgDef: IR.DefinitionArgument.Specified,
|
||||
override val passData: ISet[Metadata] = ISet()
|
||||
) extends Redefined
|
||||
with Kind.Static
|
||||
@ -2334,9 +2489,9 @@ object IR {
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
def copy(
|
||||
invalidArgDef: IR.DefinitionArgument = invalidArgDef,
|
||||
passData: ISet[Metadata] = passData,
|
||||
id: Identifier = id
|
||||
invalidArgDef: IR.DefinitionArgument.Specified = invalidArgDef,
|
||||
passData: ISet[Metadata] = passData,
|
||||
id: Identifier = id
|
||||
): Argument = {
|
||||
val res = Argument(invalidArgDef, passData)
|
||||
res.id = id
|
||||
@ -2364,6 +2519,12 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(invalidArgDef)
|
||||
|
||||
override def message: String =
|
||||
s"Argument ${invalidArgDef.name.name} is being redefined."
|
||||
|
||||
}
|
||||
|
||||
/** An error representing the redefinition of a binding in a given scope.
|
||||
@ -2418,6 +2579,12 @@ object IR {
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
|
||||
override def children: List[IR] = List(invalidBinding)
|
||||
|
||||
override def message: String =
|
||||
s"Variable ${invalidBinding.name.name} is being redefined."
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package org.enso.compiler.exception
|
||||
|
||||
import com.oracle.truffle.api.TruffleException
|
||||
import com.oracle.truffle.api.nodes.Node
|
||||
|
||||
/**
|
||||
* An exception thrown to break out of the compilation flow after reporting
|
||||
* all the encountered errors.
|
||||
*/
|
||||
class CompilationAbortedException extends Exception with TruffleException {
|
||||
override def getMessage: String = "Compilation aborted due to errors."
|
||||
|
||||
override def getLocation: Node = null
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.enso.compiler.pass.analyse
|
||||
|
||||
import org.enso.compiler.InlineContext
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/**
|
||||
* A pass that traverses the given root IR and accumulates all the encountered
|
||||
* error nodes in the root.
|
||||
*/
|
||||
case object GatherErrors extends IRPass {
|
||||
|
||||
case class Errors(errors: List[IR.Error]) extends IR.Metadata {
|
||||
/** The name of the metadata as a string. */
|
||||
override val metadataName: String = "GatherErrors.Errors"
|
||||
}
|
||||
|
||||
override type Metadata = Errors
|
||||
|
||||
/** Executes the pass on the provided `ir`, and attaches all the encountered
|
||||
* errors to its metadata storage.
|
||||
*
|
||||
* @param ir the IR to process
|
||||
* @return `ir` with all the errors accumulated in pass metadata.
|
||||
*/
|
||||
override def runModule(ir: IR.Module): IR.Module =
|
||||
ir.addMetadata(gatherErrors(ir))
|
||||
|
||||
/** Executes the pass on the provided `ir`, and attaches all the encountered
|
||||
* errors to its metadata storage.
|
||||
*
|
||||
* @param ir the IR to process
|
||||
* @param inlineContext a context object that contains the information needed
|
||||
* for inline evaluation
|
||||
* @return `ir` with all the errors accumulated in pass metadata.
|
||||
*/
|
||||
override def runExpression(
|
||||
ir: IR.Expression,
|
||||
inlineContext: InlineContext
|
||||
): IR.Expression = ir.addMetadata(gatherErrors(ir))
|
||||
|
||||
private def gatherErrors(ir: IR): Errors =
|
||||
Errors(ir.preorder.collect { case err: IR.Error => err })
|
||||
}
|
@ -106,7 +106,7 @@ case object TailCall extends IRPass {
|
||||
expression = analyseExpression(expression, isInTailPosition)
|
||||
)
|
||||
.addMetadata(TailPosition.fromBool(isInTailPosition))
|
||||
case err: IR.Error => err
|
||||
case err: IR.Error => err.addMetadata(TailPosition.fromBool(isInTailPosition))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,89 @@
|
||||
package org.enso.compiler.test.pass.analyse
|
||||
|
||||
import org.enso.compiler.InlineContext
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.CallArgument
|
||||
import org.enso.compiler.pass.analyse.GatherErrors
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.syntax.text.AST
|
||||
|
||||
class GatherErrorsTest extends CompilerTest {
|
||||
"Error Gathering" should {
|
||||
val error1 = IR.Error.Redefined.Argument(
|
||||
IR.DefinitionArgument
|
||||
.Specified(IR.Name.Literal("foo", None), None, false, None)
|
||||
)
|
||||
val error2 = IR.Error.Syntax(
|
||||
AST.Invalid.Unrecognized("@@"),
|
||||
IR.Error.Syntax.UnrecognizedToken
|
||||
)
|
||||
val plusOp = IR.Name.Literal("+", None)
|
||||
val plusApp = IR.Application.Prefix(
|
||||
plusOp,
|
||||
List(
|
||||
CallArgument.Specified(None, error1, None),
|
||||
CallArgument.Specified(None, error2, None)
|
||||
),
|
||||
false,
|
||||
None
|
||||
)
|
||||
val lam = IR.Function.Lambda(
|
||||
List(
|
||||
IR.DefinitionArgument
|
||||
.Specified(IR.Name.Literal("bar", None), None, false, None)
|
||||
),
|
||||
plusApp,
|
||||
None
|
||||
)
|
||||
|
||||
"work with expression flow" in {
|
||||
val result = GatherErrors.runExpression(lam, new InlineContext())
|
||||
val errors = result
|
||||
.unsafeGetMetadata[GatherErrors.Errors]("Impossible")
|
||||
.errors
|
||||
|
||||
errors.toSet shouldEqual Set(error1, error2)
|
||||
}
|
||||
|
||||
"work with module flow" in {
|
||||
val error3 = IR.Error.Syntax(
|
||||
AST.Invalid.Unexpected("whoa, that was not expected", List()),
|
||||
IR.Error.Syntax.UnexpectedExpression
|
||||
)
|
||||
|
||||
val error4 = IR.Error.Syntax(
|
||||
AST.Invalid.Unexpected("whoa, that was also not expected", List()),
|
||||
IR.Error.Syntax.UnexpectedExpression
|
||||
)
|
||||
|
||||
val typeName = IR.Name.Literal("Foo", None)
|
||||
val method1Name = IR.Name.Literal("bar", None)
|
||||
val method2Name = IR.Name.Literal("baz", None)
|
||||
val fooName = IR.Name.Literal("foo", None)
|
||||
|
||||
val module = IR.Module(
|
||||
List(),
|
||||
List(
|
||||
IR.Module.Scope.Definition.Atom(
|
||||
typeName,
|
||||
List(
|
||||
IR.DefinitionArgument
|
||||
.Specified(fooName, Some(error3), false, None)
|
||||
),
|
||||
None
|
||||
),
|
||||
IR.Module.Scope.Definition.Method(typeName, method1Name, lam, None),
|
||||
IR.Module.Scope.Definition.Method(typeName, method2Name, error4, None)
|
||||
),
|
||||
None
|
||||
)
|
||||
|
||||
val result = GatherErrors.runModule(module)
|
||||
val errors = result
|
||||
.unsafeGetMetadata[GatherErrors.Errors]("Impossible")
|
||||
.errors
|
||||
|
||||
errors.toSet shouldEqual Set(error1, error2, error3, error4)
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,7 @@ trait InterpreterRunner {
|
||||
.allowExperimentalOptions(true)
|
||||
.out(output)
|
||||
.build()
|
||||
val executionContext = new PolyglotContext(ctx)
|
||||
lazy val executionContext = new PolyglotContext(ctx)
|
||||
|
||||
def withLocationsInstrumenter(test: LocationsInstrumenter => Unit): Unit = {
|
||||
val instrument = ctx.getEngine.getInstruments
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.enso.interpreter.test
|
||||
|
||||
import java.io.File
|
||||
import java.io.{ByteArrayOutputStream, File}
|
||||
|
||||
import org.enso.pkg.Package
|
||||
import org.enso.polyglot.{LanguageInfo, PolyglotContext, RuntimeOptions}
|
||||
@ -20,16 +20,19 @@ trait PackageTest extends AnyFlatSpec with Matchers with ValueEquality {
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.getPackagesPathOption, pkgPath.getAbsolutePath)
|
||||
.option(RuntimeOptions.PACKAGES_PATH, pkgPath.getAbsolutePath)
|
||||
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
||||
.out(System.out)
|
||||
.in(System.in)
|
||||
.build()
|
||||
context.initialize(LanguageInfo.ID)
|
||||
val executionContext = new PolyglotContext(context)
|
||||
val topScope = executionContext.getTopScope
|
||||
val mainModuleScope = topScope.getModule(mainModule.toString)
|
||||
val assocCons = mainModuleScope.getAssociatedConstructor
|
||||
val mainFun = mainModuleScope.getMethod(assocCons, "main")
|
||||
InterpreterException.rethrowPolyglot(mainFun.execute(assocCons))
|
||||
InterpreterException.rethrowPolyglot {
|
||||
val topScope = executionContext.getTopScope
|
||||
val mainModuleScope = topScope.getModule(mainModule.toString)
|
||||
val assocCons = mainModuleScope.getAssociatedConstructor
|
||||
val mainFun = mainModuleScope.getMethod(assocCons, "main")
|
||||
mainFun.execute(assocCons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,9 @@ import org.scalactic.Equality
|
||||
trait ValueEquality {
|
||||
implicit val valueEquality: Equality[Value] = (a: Value, b: Any) =>
|
||||
b match {
|
||||
case _: Long => a.isNumber && a.fitsInLong && a.asLong == b
|
||||
case _: Int => a.isNumber && a.fitsInInt && a.asInt == b
|
||||
case _ => false
|
||||
case _: Long => a.isNumber && a.fitsInLong && a.asLong == b
|
||||
case _: Int => a.isNumber && a.fitsInInt && a.asInt == b
|
||||
case _: String => a.isString && a.asString == b
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class RuntimeServerTest
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.getPackagesPathOption, pkg.root.getAbsolutePath)
|
||||
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
|
||||
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
|
||||
.out(out)
|
||||
.serverTransport { (uri, peer) =>
|
||||
@ -383,7 +383,9 @@ class RuntimeServerTest
|
||||
)
|
||||
|
||||
// recompute
|
||||
context.send(Api.Request(requestId, Api.RecomputeContextRequest(contextId, None)))
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
)
|
||||
Set.fill(5)(context.receive) shouldEqual Set(
|
||||
Some(Api.Response(requestId, Api.RecomputeContextResponse(contextId))),
|
||||
Some(Main.update.idMainX(contextId)),
|
||||
|
@ -0,0 +1,40 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.InterpreterTest
|
||||
|
||||
class CompileErrorsTest extends InterpreterTest {
|
||||
"ast-processing errors" should "be surfaced in the language" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| x = Panic.recover ()
|
||||
| x.catch err->
|
||||
| case err of
|
||||
| Syntax_Error msg -> "Oopsie, it's a syntax error: " + msg
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual "Oopsie, it's a syntax error: Parentheses can't be empty."
|
||||
}
|
||||
|
||||
"parsing errors" should "be surfaced in the language" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| x = Panic.recover @
|
||||
| x.catch to_text
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual "Syntax_Error Unrecognized token."
|
||||
}
|
||||
|
||||
"redefinition errors" should "be surfaced in the language" in {
|
||||
val code =
|
||||
"""
|
||||
|foo =
|
||||
| x = 1
|
||||
| x = 2
|
||||
|
|
||||
|main = Panic.recover here.foo . catch to_text
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual "Compile_Error Variable x is being redefined."
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.{
|
||||
InterpreterException,
|
||||
InterpreterTest,
|
||||
}
|
||||
import org.enso.polyglot.{LanguageInfo, RuntimeOptions}
|
||||
import org.graalvm.polyglot.Context
|
||||
|
||||
class StrictCompileErrorsTest extends InterpreterTest {
|
||||
override val ctx: Context = Context
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
||||
.out(output)
|
||||
.build()
|
||||
|
||||
"Compile errors in batch mode" should "be reported and abort execution" in {
|
||||
val code =
|
||||
"""main =
|
||||
| x = ()
|
||||
| x = 5
|
||||
| y = @
|
||||
|""".stripMargin.lines.mkString("\n")
|
||||
|
||||
the[InterpreterException] thrownBy eval(code) should have message "Compilation aborted due to errors."
|
||||
val _ :: errors = consumeOut
|
||||
errors.toSet shouldEqual Set(
|
||||
"Test[2:9-2:10]: Parentheses can't be empty.",
|
||||
"Test[3:5-3:9]: Variable x is being redefined.",
|
||||
"Test[4:9-4:9]: Unrecognized token."
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user