From 58e4f49fa17aa1da4964c81d120cb41b09d07a13 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 20 Oct 2025 11:53:09 +0200 Subject: [PATCH 1/6] Abort type-check when dependencies have parse errors. --- .../lsp/lang/rascal/lsp/IDECheckerWrapper.rsc | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc index d8ffd1074..b2c6616f9 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc @@ -36,6 +36,7 @@ import Location; import analysis::graphs::Graph; import util::FileSystem; import util::Monitor; +import util::ParseErrorRecovery; import util::Reflective; import lang::rascal::\syntax::Rascal; @@ -53,27 +54,41 @@ import lang::rascalcore::check::ModuleLocations; } map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module](loc file) getParseTree, PathConfig(loc file) getPathConfig) = job("Rascal check", map[loc, set[Message]](void(str, int) step) { - checkForImports = [getParseTree(l)]; + openFile = getParseTree(l); + openFileHeader = openFile.top.header.name; + + if (hasParseErrors(openFile)) { + // We cannot typecheck this file, since it has type errors. Do not return any errors, since the parse triggered by the IDE will take care of that. + return (); + } + + checkForImports = [openFile]; checkedForImports = {}; initialProject = inferProjectRoot(l); rel[loc, loc] dependencies = {}; step("Dependency graph", 1); - job("Building dependency graph", bool (void (str, int) step2) { + parseMsgs = job("Building dependency graph", rel[loc, Message] (void (str, int) step2) { while (tree <- checkForImports) { step2("Calculating imports for ", 1); currentSrc = tree.src.top; currentProject = inferProjectRoot(currentSrc); if (currentProject in workspaceFolders && currentProject.file notin {"rascal", "rascal-lsp"}) { for (i <- tree.top.header.imports, i has \module) { + modName = ""; try { - ml = locateRascalModule("", getPathConfig(currentProject), getPathConfig, workspaceFolders); + ml = locateRascalModule(modName, getPathConfig(currentProject), getPathConfig, workspaceFolders); if (ml.extension == "rsc", mlpt := getParseTree(ml), mlpt.src.top notin checkedForImports) { + if (printlnExp("Transitive dependency has parse error(s): ", hasParseErrors(mlpt))) { + return { has parse error(s) ().", openFileHeader.src)>}; + } checkForImports += mlpt; jobTodo("Building dependency graph"); dependencies += ; } + } catch ParseError(loc l): { + return { has parse error(s) ().", openFileHeader.src)>}; } catch _: { ;// Continue } @@ -82,9 +97,13 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] checkedForImports += currentSrc; checkForImports -= tree; } - return true; + return {}; }, totalWork=1); + if ({} !:= parseMsgs) { + return toMap(parseMsgs); + } + cyclicDependencies = {p | <- (dependencies - ident(carrier(dependencies)))+}; if (cyclicDependencies != {}) { return (l : {error("Cyclic dependencies detected between projects {}. This is not supported. Fix your project setup.", l)}); From 0b596b6dec03f1ad9c6e62803a41f4b9ba8a1e62 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 20 Oct 2025 13:14:57 +0200 Subject: [PATCH 2/6] Remove debug message. --- .../src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc index b2c6616f9..9ca8d7f5f 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc @@ -80,7 +80,7 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] try { ml = locateRascalModule(modName, getPathConfig(currentProject), getPathConfig, workspaceFolders); if (ml.extension == "rsc", mlpt := getParseTree(ml), mlpt.src.top notin checkedForImports) { - if (printlnExp("Transitive dependency has parse error(s): ", hasParseErrors(mlpt))) { + if (hasParseErrors(mlpt)) { return { has parse error(s) ().", openFileHeader.src)>}; } checkForImports += mlpt; From e31ddeb6be70db4d662bfe00f1a42c6322e838a3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 23 Oct 2025 14:05:22 +0200 Subject: [PATCH 3/6] Improve error messages in case of parse errors. --- .../lsp/lang/rascal/lsp/IDECheckerWrapper.rsc | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc index 9ca8d7f5f..3d9a33dbc 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc @@ -54,13 +54,17 @@ import lang::rascalcore::check::ModuleLocations; } map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module](loc file) getParseTree, PathConfig(loc file) getPathConfig) = job("Rascal check", map[loc, set[Message]](void(str, int) step) { - openFile = getParseTree(l); - openFileHeader = openFile.top.header.name; - + start[Module] openFile; + try { + openFile = getParseTree(l); + } catch ParseError(loc err): { + return (l: {error("Cannot typecheck this module, since it has parse error(s).", err)}); + } if (hasParseErrors(openFile)) { // We cannot typecheck this file, since it has type errors. Do not return any errors, since the parse triggered by the IDE will take care of that. return (); } + openFileHeader = openFile.top.header.name; checkForImports = [openFile]; checkedForImports = {}; @@ -69,7 +73,7 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] rel[loc, loc] dependencies = {}; step("Dependency graph", 1); - parseMsgs = job("Building dependency graph", rel[loc, Message] (void (str, int) step2) { + parseMsgs = job("Building dependency graph", map[loc, set[Message]] (void (str, int) step2) { while (tree <- checkForImports) { step2("Calculating imports for ", 1); currentSrc = tree.src.top; @@ -81,15 +85,18 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] ml = locateRascalModule(modName, getPathConfig(currentProject), getPathConfig, workspaceFolders); if (ml.extension == "rsc", mlpt := getParseTree(ml), mlpt.src.top notin checkedForImports) { if (hasParseErrors(mlpt)) { - return { has parse error(s) ().", openFileHeader.src)>}; + return (l: {error("Cannot typecheck this module.", openFileHeader.src, + causes=[error("Parse error", e.src) | Tree e <- findBestParseErrors(mlpt)]) + }); } checkForImports += mlpt; jobTodo("Building dependency graph"); dependencies += ; } - } catch ParseError(loc l): { - return { has parse error(s) ().", openFileHeader.src)>}; - } catch _: { + } catch ParseError(loc err): { + return (l: {error("Cannot typecheck this module.", openFileHeader.src, causes=[error(" has parse error(s).", err)])}); + } catch e: { + println("Exception while building dependency graph at : "); ;// Continue } } @@ -97,11 +104,12 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] checkedForImports += currentSrc; checkForImports -= tree; } - return {}; + + return (); }, totalWork=1); - if ({} !:= parseMsgs) { - return toMap(parseMsgs); + if (() != parseMsgs) { + return parseMsgs; } cyclicDependencies = {p | <- (dependencies - ident(carrier(dependencies)))+}; From abf93b2a789d142c71f74f60202685a0668adfec Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 23 Oct 2025 14:22:24 +0200 Subject: [PATCH 4/6] Align error messages. --- .../main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc index 3d9a33dbc..7437ee279 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc @@ -85,8 +85,8 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] ml = locateRascalModule(modName, getPathConfig(currentProject), getPathConfig, workspaceFolders); if (ml.extension == "rsc", mlpt := getParseTree(ml), mlpt.src.top notin checkedForImports) { if (hasParseErrors(mlpt)) { - return (l: {error("Cannot typecheck this module.", openFileHeader.src, - causes=[error("Parse error", e.src) | Tree e <- findBestParseErrors(mlpt)]) + return (l: {error("Cannot typecheck this module, since a dependency has parse error(s).", openFileHeader.src, + causes=[error("Has a parse error around this position.", e.src) | Tree e <- findBestParseErrors(mlpt)]) }); } checkForImports += mlpt; @@ -94,7 +94,7 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] dependencies += ; } } catch ParseError(loc err): { - return (l: {error("Cannot typecheck this module.", openFileHeader.src, causes=[error(" has parse error(s).", err)])}); + return (l: {error("Cannot typecheck this module, since a dependency has parse error(s).", openFileHeader.src, causes=[error("Has parse error(s).", err)])}); } catch e: { println("Exception while building dependency graph at : "); ;// Continue From 8b7d84825221c5a07a81de20dab38269e3e65f64 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 28 Oct 2025 09:38:27 +0100 Subject: [PATCH 5/6] Mention transitive import name in error message. --- .../src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc index 7437ee279..fce6c197c 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc @@ -85,7 +85,7 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] ml = locateRascalModule(modName, getPathConfig(currentProject), getPathConfig, workspaceFolders); if (ml.extension == "rsc", mlpt := getParseTree(ml), mlpt.src.top notin checkedForImports) { if (hasParseErrors(mlpt)) { - return (l: {error("Cannot typecheck this module, since a dependency has parse error(s).", openFileHeader.src, + return (l: {error("Cannot typecheck this module, since dependency `` has parse error(s).", openFileHeader.src, causes=[error("Has a parse error around this position.", e.src) | Tree e <- findBestParseErrors(mlpt)]) }); } @@ -94,7 +94,7 @@ map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module] dependencies += ; } } catch ParseError(loc err): { - return (l: {error("Cannot typecheck this module, since a dependency has parse error(s).", openFileHeader.src, causes=[error("Has parse error(s).", err)])}); + return (l: {error("Cannot typecheck this module, since dependency `` has parse error(s).", openFileHeader.src, causes=[error("Has parse error(s).", err)])}); } catch e: { println("Exception while building dependency graph at : "); ;// Continue From a2e72ed735f25a8820669a535058763fcb5d8e47 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 3 Nov 2025 11:29:23 +0100 Subject: [PATCH 6/6] Align messages for recovered/unrecovered errors. --- .../main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc index fce6c197c..1fd122cdc 100644 --- a/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc +++ b/rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/IDECheckerWrapper.rsc @@ -54,11 +54,12 @@ import lang::rascalcore::check::ModuleLocations; } map[loc, set[Message]] checkFile(loc l, set[loc] workspaceFolders, start[Module](loc file) getParseTree, PathConfig(loc file) getPathConfig) = job("Rascal check", map[loc, set[Message]](void(str, int) step) { - start[Module] openFile; + openFile = (start[Module]) `module Placeholder`; try { + // Note: check further down parses again, possibly leading to a different tree if the contents changed in the meantime openFile = getParseTree(l); } catch ParseError(loc err): { - return (l: {error("Cannot typecheck this module, since it has parse error(s).", err)}); + return (); } if (hasParseErrors(openFile)) { // We cannot typecheck this file, since it has type errors. Do not return any errors, since the parse triggered by the IDE will take care of that.