From 0c4f35b751246c86c6fe15ada7aedf062ff10ee3 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Mon, 6 Apr 2026 11:31:29 +0200 Subject: [PATCH 1/5] fixed #2747 and a number of other error scenarios like wrong module names --- src/org/rascalmpl/semantics/dynamic/Import.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/org/rascalmpl/semantics/dynamic/Import.java b/src/org/rascalmpl/semantics/dynamic/Import.java index 276bb4173f2..a42063cd6fb 100644 --- a/src/org/rascalmpl/semantics/dynamic/Import.java +++ b/src/org/rascalmpl/semantics/dynamic/Import.java @@ -344,11 +344,17 @@ public static ModuleEnvironment loadModule(ISourceLocation x, String name, IEval ISourceLocation uri = eval.getRascalResolver().resolveModule(name); + if (uri == null) { + heap.setModuleURI(jobName, URIUtil.correctLocation("not-found", name, "").getURI()); + throw new ModuleImport(name, "can not find in search path", x); + } + else { + heap.setModuleURI(name, uri.getURI()); + } + try { eval.jobTodo(jobName, 1); - if (uri == null) { - throw new ModuleImport(name, "can not find in search path", x); - } + Module module = buildModule(uri, env, eval, jobName); if (isDeprecated(module)) { @@ -360,7 +366,7 @@ public static ModuleEnvironment loadModule(ISourceLocation x, String name, IEval if (!internalName.equals(name)) { throw new ModuleNameMismatch(internalName, name, x); } - heap.setModuleURI(name, module.getLocation().getURI()); + module.interpret(eval); } @@ -434,7 +440,7 @@ private static ASTBuilder getBuilder() { } private static void addImportToCurrentModule(ISourceLocation src, String name, IEvaluator> eval) { - ModuleEnvironment module = eval.getHeap().getModule(name); + ModuleEnvironment module = eval.getHeap().getModule(name); if (module == null) { throw new UndeclaredModule(name, src); } From 49bd9e42a6ba58f5dad3d3ba3d512577bdc977c7 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Mon, 6 Apr 2026 20:15:45 +0200 Subject: [PATCH 2/5] made module load messages observable to util::Eval::RascalRuntime --- .../interpreter/env/GlobalEnvironment.java | 9 +++++++++ .../interpreter/env/ModuleEnvironment.java | 5 +++++ src/org/rascalmpl/library/util/Eval.java | 16 +++++++++++++++- src/org/rascalmpl/library/util/Eval.rsc | 5 ++++- 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/interpreter/env/GlobalEnvironment.java b/src/org/rascalmpl/interpreter/env/GlobalEnvironment.java index 7afa17c4565..b7f1bd4ce6b 100644 --- a/src/org/rascalmpl/interpreter/env/GlobalEnvironment.java +++ b/src/org/rascalmpl/interpreter/env/GlobalEnvironment.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.rascalmpl.ast.AbstractAST; import org.rascalmpl.ast.QualifiedName; @@ -34,6 +35,7 @@ import org.rascalmpl.values.IRascalValueFactory; import io.usethesource.capsule.SetMultimap; +import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; import io.usethesource.vallang.IString; @@ -329,4 +331,11 @@ public void clearLookupChaches() { public void clearModuleLoadMessage() { moduleEnvironment.values().forEach(ModuleEnvironment::clearLoadMessages); } + + public Stream streamModuleLoadMessages() { + return moduleEnvironment + .values() + .stream() + .flatMap(me -> me.streamLoadMessages()); + } } diff --git a/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java b/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java index 98a1bf90269..495a090ae6c 100644 --- a/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java +++ b/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java @@ -32,6 +32,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; import org.rascalmpl.ast.AbstractAST; import org.rascalmpl.ast.KeywordFormal; @@ -143,6 +144,10 @@ public void clearLookupCaches() { public void clearLoadMessages() { this.loadMessages.clear(); } + + public Stream streamLoadMessages() { + return loadMessages.stream(); + } public void extend(ModuleEnvironment other) { extendNameFlags(other); diff --git a/src/org/rascalmpl/library/util/Eval.java b/src/org/rascalmpl/library/util/Eval.java index f79f9fd60db..5b7c46259d8 100644 --- a/src/org/rascalmpl/library/util/Eval.java +++ b/src/org/rascalmpl/library/util/Eval.java @@ -20,6 +20,7 @@ import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.exceptions.RuntimeExceptionFactory; @@ -31,6 +32,7 @@ import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.interpreter.staticErrors.StaticError; import org.rascalmpl.interpreter.staticErrors.UnexpectedType; +import org.rascalmpl.library.Messages; import org.rascalmpl.shell.ShellEvaluatorFactory; import org.rascalmpl.types.RascalTypeFactory; import org.rascalmpl.types.TypeReifier; @@ -40,6 +42,8 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IInteger; +import io.usethesource.vallang.IList; +import io.usethesource.vallang.ISet; import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; import io.usethesource.vallang.type.Type; @@ -58,7 +62,8 @@ public class Eval { public final Type Result_void = tf.constructor(store, Result, "ok"); public final Type Result_value = tf.constructor(store, Result, "result", param, "val"); public final Type Exception = tf.abstractDataType(store, "Exception"); - public final Type Exception_StaticError = tf.constructor(store, Exception, "StaticError", tf.stringType(), "messages", tf.sourceLocationType(), "location"); + public final Type Exception_StaticError = tf.constructor(store, Exception, "StaticError", tf.stringType(), "message", tf.sourceLocationType(), "location"); + public final Type Exception_LoadMessages = tf.constructor(store, Exception, "ModuleLoadMessages", tf.listType(Messages.Message), "messages"); private final Type resetType = tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty()); private final Type setTimeoutType = tf.functionType(tf.voidType(), tf.tupleType(tf.integerType()), tf.tupleEmpty()); private final Type evalType = tf.functionType(Result_value, tf.tupleType(TypeTyp, tf.stringType()), tf.tupleEmpty()); @@ -175,6 +180,11 @@ private IFunction buildEvalFunction(RascalRuntime exec) { throw new UnexpectedType(typ, result.getStaticType(), URIUtil.rootLocation("eval")); } + IList loadMessages = exec.moduleLoadMessages(); + if (loadMessages.stream().anyMatch(c -> ((IConstructor) c).getName().equals("error"))) { + throw new Throw(values.constructor(Exception_LoadMessages, loadMessages), null, null); + } + if (result.getStaticType().isBottom()) { return values.constructor(Result_void); } @@ -231,6 +241,10 @@ public void reset() { eval.getHeap().clear(); } + public IList moduleLoadMessages() { + return eval.__getHeap().streamModuleLoadMessages().collect(eval.getValueFactory().listWriter()); + } + public Result eval(IRascalMonitor monitor, String line) throws InterruptedException, IOException { return eval.eval(monitor, line, IRascalValueFactory.getInstance().sourceLocation(URIUtil.assumeCorrect("eval", "", "", "command=" + line))); } diff --git a/src/org/rascalmpl/library/util/Eval.rsc b/src/org/rascalmpl/library/util/Eval.rsc index daaa76da7c5..b828edf21da 100644 --- a/src/org/rascalmpl/library/util/Eval.rsc +++ b/src/org/rascalmpl/library/util/Eval.rsc @@ -13,7 +13,9 @@ module util::Eval extend Exception; extend util::Reflective; + import IO; +import Message; @synopsis{Results encode the output of a call to `eval`} @description{ @@ -31,6 +33,7 @@ data Result[&T] } data RuntimeException = StaticError(str message, loc location) + | ModuleLoadMessages(list[Message] messages) ; @synopsis{A reusable instance of the Rascal runtime system configured by a specific PathConfig.} @@ -96,7 +99,7 @@ This creates a ((RascalRuntime)), uses it to evaluate one command, and then disc } @deprecated{Use ((createRascalRuntime)) for better efficiency/configurability.} Result[&T] eval(type[&T] typ, str command, int duration=-1, PathConfig pcfg=pathConfig()) - throws Timeout, StaticError, ParseError + throws Timeout, StaticError, ParseError, ModuleLoadMessages = eval(typ, [command], pcfg=pcfg, duration=duration); @synopsis{Evaluate a list of command and return the value of the last command.} From 50de1e33ffba13490841b9833fd993bee6ae72b0 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Mon, 6 Apr 2026 20:35:01 +0200 Subject: [PATCH 3/5] added a minimal set of load tests in the presence of errors in modules, that trigger #2747 --- .../tests/loading/LoadingErrorModules.rsc | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc diff --git a/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc b/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc new file mode 100644 index 00000000000..c372d0d899b --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc @@ -0,0 +1,68 @@ +module lang::rascal::tests::loading::LoadingErrorModules + +import IO; +import util::Eval; +import util::PathConfig; + +PathConfig init() = pathConfig(srcs=[|memory://LoadingErrorModules/|]); + +loc moduleFile(str name) = |memory://LoadingErrorModules/| + ".rsc"; + +test bool moduleWithParseError() { + exec = createRascalRuntime(pcfg=init()); + + writeFile(moduleFile("A"), "modle A"); + + try { + exec.eval(#void, "import A;"); + return false; + } + catch ModuleLoadMessages([error(_,_)]): { + // that's ok + ; + } + + writeFile(moduleFile("A"), "module A"); + + return exec.eval(#void, "import A;") == ok(); +} + +test bool moduleWithTransitiveParseError() { + exec = createRascalRuntime(pcfg=init()); + + writeFile(moduleFile("A"), "modle A"); + writeFile(moduleFile("B"), "module B import A;"); + + try { + exec.eval(#void, "import B;"); + return false; + } + catch ModuleLoadMessages([error(_,_)]): { + // that's ok + ; + } + + writeFile(moduleFile("A"), "module A"); + + return exec.eval(#void, "import A;") == ok() + && exec.eval(#void, "import B;") == ok(); +} + +test bool moduleWithStaticError() { + exec = createRascalRuntime(pcfg=init()); + + writeFile(moduleFile("A"), "module A str aap = 42;"); + + try { + exec.eval(#void, "import A;"); + return false; + } + catch ModuleLoadMessages([error(_,_)]): { + // that's ok + ; + } + + writeFile(moduleFile("A"), "module A str aap = \"42\";"); + + return exec.eval(#void, "import A;") == ok(); +} \ No newline at end of file From bf4bbd0efa9eff954f07663d051ee654f4721e7e Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Mon, 6 Apr 2026 20:37:52 +0200 Subject: [PATCH 4/5] organized imports --- src/org/rascalmpl/library/util/Eval.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/org/rascalmpl/library/util/Eval.java b/src/org/rascalmpl/library/util/Eval.java index 5b7c46259d8..f38d4f439f8 100644 --- a/src/org/rascalmpl/library/util/Eval.java +++ b/src/org/rascalmpl/library/util/Eval.java @@ -20,8 +20,6 @@ import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; -import java.util.stream.Stream; - import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.exceptions.Throw; @@ -43,7 +41,6 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IInteger; import io.usethesource.vallang.IList; -import io.usethesource.vallang.ISet; import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; import io.usethesource.vallang.type.Type; From af234148fc90c2a4590a063fd1efa5fc95d661a7 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Mon, 6 Apr 2026 20:43:32 +0200 Subject: [PATCH 5/5] added more tests --- .../tests/loading/LoadingErrorModules.rsc | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc b/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc index c372d0d899b..3a804e6cf12 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/loading/LoadingErrorModules.rsc @@ -3,6 +3,7 @@ module lang::rascal::tests::loading::LoadingErrorModules import IO; import util::Eval; import util::PathConfig; +import Message; PathConfig init() = pathConfig(srcs=[|memory://LoadingErrorModules/|]); @@ -27,6 +28,27 @@ test bool moduleWithParseError() { return exec.eval(#void, "import A;") == ok(); } +test bool moduleWithTransientParseError() { + exec = createRascalRuntime(pcfg=init()); + + writeFile(moduleFile("A"), "module A"); + assert exec.eval(#void, "import A;") == ok(); + writeFile(moduleFile("A"), "modle A"); + + try { + exec.eval(#void, "import A;"); + return false; + } + catch ModuleLoadMessages([error(_,_)]): { + // that's ok + ; + } + + writeFile(moduleFile("A"), "module A"); + + return exec.eval(#void, "import A;") == ok(); +} + test bool moduleWithTransitiveParseError() { exec = createRascalRuntime(pcfg=init()); @@ -65,4 +87,44 @@ test bool moduleWithStaticError() { writeFile(moduleFile("A"), "module A str aap = \"42\";"); return exec.eval(#void, "import A;") == ok(); -} \ No newline at end of file +} + +test bool importNonExistingModule() { + exec = createRascalRuntime(pcfg=init()); + + try { + exec.eval(#void, "import Z;"); + return false; + } + catch ModuleLoadMessages([error(_,_)]): { + // that's ok + ; + } + + writeFile(moduleFile("Z"), "module Z public str aap = \"aap\";"); + + return exec.eval(#void, "import Z;") == ok() + && result("aap") == exec.eval(#str, "aap"); +} + + +test bool importBrokenModuleName() { + exec = createRascalRuntime(pcfg=init()); + + writeFile(moduleFile("AAA"), "module AA public str aap = \"aap\";"); + + try { + exec.eval(#void, "import AAA;"); + return false; + } + catch ModuleLoadMessages([error(_,_)]): { + // that's ok + ; + } + + writeFile(moduleFile("AAA"), "module AAA public str aap = \"aap\";"); + + return exec.eval(#void, "import AAA;") == ok() + && result("aap") == exec.eval(#str, "aap"); +} +