Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error recovery for Rascal #490

Draft
wants to merge 39 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
20948c6
Update `parse` call to use error recovery
sungshik Oct 8, 2024
ea87d9e
Update `pom.xml` for development
sungshik Oct 8, 2024
6fa7a82
Add reporting of diagnostics for error nodes
sungshik Oct 8, 2024
1aa3634
Make error recovery backward-compatible
sungshik Oct 8, 2024
35a3b02
Add flag to `loadParser` call to set allowRecovery to false for now
sungshik Oct 8, 2024
ea9919f
Fix a bug that adds irrelevant diagnostics
sungshik Oct 9, 2024
2d5dc04
Add exception handler when the location of a parse error is malformed
sungshik Oct 9, 2024
9643852
Add first version parse debouncing
sungshik Oct 11, 2024
f94f08f
Merge branch 'main' into error-recovery/rascal
PieterOlivier Oct 15, 2024
04eaedd
Revert "Add first version parse debouncing"
PieterOlivier Oct 15, 2024
a3dbfbd
Show skipped part and use error tree for outlining
PieterOlivier Oct 16, 2024
17bcab1
Add debounce to parsing in `TextDocumentState`
sungshik Oct 24, 2024
b085657
Merge branch 'error-recovery/rascal' into error-recovery/rascal-debou…
sungshik Oct 24, 2024
6b35288
Use `compareAndSet` instead of `weak...`
sungshik Oct 24, 2024
2749e49
Fix typos in documentation
sungshik Oct 24, 2024
f1bacae
Merge pull request #475 from usethesource/error-recovery/rascal-debou…
sungshik Oct 24, 2024
9d803d0
Merge pull request #476 from usethesource/recovery/skipped
PieterOlivier Oct 25, 2024
b947d04
Upgrade `pom.xml` and `package.sh`
sungshik Oct 25, 2024
073b675
Both outline and codelenses support can now handle error trees
PieterOlivier Oct 25, 2024
5d60fde
Removed spurious hasErrors
PieterOlivier Oct 25, 2024
91d0e6b
Merge pull request #491 from usethesource/recovery/outline-and-code-l…
PieterOlivier Oct 25, 2024
a9d5a06
Move parse error processing (including error nodes) completely to `Te…
sungshik Oct 25, 2024
770561e
Merge branch 'error-recovery/rascal' into error-recovery/rascal-diagn…
sungshik Oct 25, 2024
aeb1a9f
Refine parse error messages
sungshik Oct 25, 2024
90ab10c
Remove unused imports
sungshik Oct 28, 2024
d86c659
Move class `Debouncer` to its own file
sungshik Oct 28, 2024
b095617
Merge pull request #492 from usethesource/error-recovery/rascal-diagn…
sungshik Oct 30, 2024
ea02b93
Merge branch 'main' into error-recovery/rascal
PieterOlivier Nov 5, 2024
92cc913
Remove function that should have been deleted during merge from main
PieterOlivier Nov 5, 2024
066c5df
Bumped rascal version number
PieterOlivier Nov 6, 2024
ed3dd87
Simplify debouncer (joint with @PieterOlivier)
sungshik Nov 8, 2024
7f16037
Improve API of `DebouncedSupplier`
sungshik Nov 8, 2024
761ab06
Add tests for `DebouncedSupplier`
sungshik Nov 8, 2024
15aaa0f
Improve documentations of `DebouncedSupplier`
sungshik Nov 8, 2024
b81dfc4
Revert signature change to `parseIfNotParsing`
sungshik Nov 8, 2024
87e5959
Rename methods to make the names more precise
sungshik Nov 8, 2024
9653e26
Merge branch 'main' into error-recovery/rascal
PieterOlivier Nov 10, 2024
5a123b2
Merge pull request #512 from usethesource/error-recovery/rascal-simpl…
sungshik Nov 11, 2024
819d7a3
Merge branch 'main' into error-recovery/rascal
PieterOlivier Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rascal-lsp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
<dependency>
<groupId>org.rascalmpl</groupId>
<artifactId>rascal</artifactId>
<version>0.40.11</version>
<version>0.40.9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.rascalmpl</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@
import java.util.function.BiFunction;

import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.rascalmpl.library.util.ErrorRecovery;
import org.rascalmpl.values.RascalValueFactory;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.util.Versioned;

import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISourceLocation;

/**
Expand All @@ -51,7 +55,9 @@ public class TextDocumentState {
@SuppressWarnings("java:S3077") // we are use volatile correctly
private volatile Versioned<String> currentContent;
@SuppressWarnings("java:S3077") // we are use volatile correctly
private volatile @MonotonicNonNull Versioned<ITree> lastFullTree;
private volatile @MonotonicNonNull Versioned<ITree> lastTree;
@SuppressWarnings("java:S3077") // we are use volatile correctly
private volatile @MonotonicNonNull Versioned<ITree> lastTreeWithoutErrors;
@SuppressWarnings("java:S3077") // we are use volatile correctly
private volatile CompletableFuture<Versioned<ITree>> currentTree;

Expand Down Expand Up @@ -83,9 +89,14 @@ public CompletableFuture<Versioned<ITree>> update(int version, String content) {
private CompletableFuture<Versioned<ITree>> newTreeAsync(int version, String content) {
return parser.apply(file, content)
.thenApply(t -> new Versioned<ITree>(version, t))
.whenComplete((r, t) -> {
if (r != null) {
lastFullTree = r;
.whenComplete((t, error) -> {
if (t != null) {
RascalValueFactory valueFactory = (RascalValueFactory) ValueFactoryFactory.getValueFactory();
IList errors = new ErrorRecovery(valueFactory).findAllErrors(t.get());
if (errors.isEmpty()) {
lastTreeWithoutErrors = t;
}
lastTree = t;
}
});
}
Expand All @@ -94,8 +105,12 @@ public CompletableFuture<Versioned<ITree>> getCurrentTreeAsync() {
return currentTree;
}

public @MonotonicNonNull Versioned<ITree> getMostRecentTree() {
return lastFullTree;
public @MonotonicNonNull Versioned<ITree> getLastTree() {
return lastTree;
}

public @MonotonicNonNull Versioned<ITree> getLastTreeWithoutErrors() {
return lastTreeWithoutErrors;
}

public ISourceLocation getLocation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public CompletableFuture<List<InlayHint>> inlayHint(InlayHintParams params) {
final TextDocumentState file = getFile(params.getTextDocument());
final ILanguageContributions contrib = contributions(params.getTextDocument());
return recoverExceptions(
recoverExceptions(file.getCurrentTreeAsync(), file::getMostRecentTree)
recoverExceptions(file.getCurrentTreeAsync(), file::getLastTreeWithoutErrors)
.thenApply(Versioned::get)
.thenApply(contrib::inlayHint)
.thenCompose(InterruptibleFuture::get)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private static Either<IFunction, Exception> loadParser(ParserSpecification spec)
try {
logger.debug("Loading parser {} at {}", reifiedType, spec.getParserLocation());
// this hides all the loading and instantiation details of Rascal-generated parsers
var parser = vf.loadParser(reifiedType, spec.getParserLocation(), VF.bool(spec.getAllowAmbiguity()), VF.bool(false), VF.bool(false), vf.set());
var parser = vf.loadParser(reifiedType, spec.getParserLocation(), VF.bool(spec.getAllowAmbiguity()), VF.bool(false), VF.bool(false), VF.bool(false), vf.set());
logger.debug("Got parser: {}", parser);
return Either.forLeft(parser);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.rascalmpl.exceptions.Throw;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.library.util.ErrorRecovery;
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.functions.IFunction;
Expand All @@ -76,6 +77,7 @@

public class RascalLanguageServices {
private static final IValueFactory VF = IRascalValueFactory.getInstance();
private static final ErrorRecovery RECOVERY = new ErrorRecovery(IRascalValueFactory.getInstance());

private static final Logger logger = LogManager.getLogger(RascalLanguageServices.class);

Expand Down Expand Up @@ -236,8 +238,13 @@ public List<CodeLensSuggestion> locateCodeLenses(ITree tree) {
List<CodeLensSuggestion> result = new ArrayList<>(2);
result.add(new CodeLensSuggestion(module, "Import in new Rascal terminal", "rascalmpl.importModule", moduleName));

for (IValue topLevel : TreeAdapter
.getListASTArgs(TreeAdapter.getArg(TreeAdapter.getArg(tree, "body"), "toplevels"))) {
ITree body = TreeAdapter.getArg(tree, "body");
ITree toplevels = TreeAdapter.getArg(body, "toplevels");
for (IValue topLevel : TreeAdapter.getListASTArgs(toplevels)) {
if (RECOVERY.hasErrors((ITree) topLevel)) {
continue;
}

ITree decl = TreeAdapter.getArg((ITree) topLevel, "declaration");
if ("function".equals(TreeAdapter.getConstructorName(decl))) {
ITree signature = TreeAdapter.getArg(TreeAdapter.getArg(decl, "functionDeclaration"), "signature");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.rascalmpl.library.Prelude;
import org.rascalmpl.library.util.ErrorRecovery;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.values.RascalValueFactory;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.BaseWorkspaceService;
import org.rascalmpl.vscode.lsp.IBaseLanguageClient;
Expand All @@ -105,6 +108,7 @@
import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap;
import org.rascalmpl.vscode.lsp.util.locations.Locations;

import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValue;

Expand Down Expand Up @@ -215,26 +219,35 @@ private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, St

private void handleParsingErrors(TextDocumentState file, CompletableFuture<Versioned<ITree>> futureTree) {
futureTree.handle((tree, excp) -> {
Diagnostic newParseError = null;
if (excp != null && excp instanceof CompletionException) {
List<Diagnostic> parseErrors = new ArrayList<>();

if (excp instanceof CompletionException) {
excp = excp.getCause();
}

if (excp instanceof ParseError) {
newParseError = Diagnostics.translateDiagnostic((ParseError)excp, columns);
}
else if (excp != null) {
parseErrors.add(Diagnostics.translateDiagnostic((ParseError)excp, columns));
} else if (excp != null) {
logger.error("Parsing crashed", excp);
newParseError = new Diagnostic(
parseErrors.add(new Diagnostic(
new Range(new Position(0,0), new Position(0,1)),
"Parsing failed: " + excp.getMessage(),
DiagnosticSeverity.Error,
"Rascal Parser");
"Rascal Parser"));
}

if (tree != null) {
RascalValueFactory valueFactory = (RascalValueFactory) ValueFactoryFactory.getValueFactory();
IList errors = new ErrorRecovery(valueFactory).findAllErrors(tree.get());
for (IValue error : errors) {
ITree errorTree = (ITree) error;
parseErrors.add(Diagnostics.translateErrorRecoveryDiagnostic(errorTree, columns));
}
}
logger.trace("Finished parsing tree, reporting new parse error: {} for: {}", newParseError, file.getLocation());

logger.trace("Finished parsing tree, reporting new parse errors: {} for: {}", parseErrors, file.getLocation());
if (facts != null) {
facts.reportParseErrors(file.getLocation(),
newParseError == null ? Collections.emptyList() : Collections.singletonList(newParseError));
facts.reportParseErrors(file.getLocation(), parseErrors);
}
return null;
});
Expand Down Expand Up @@ -267,7 +280,7 @@ public CompletableFuture<Either<List<? extends Location>, List<? extends Locatio
TextDocumentState file = getFile(params.getTextDocument());
return file.getCurrentTreeAsync()
.thenApply(Versioned::get)
.handle((t, r) -> (t == null ? (file.getMostRecentTree().get()) : t))
.handle((t, r) -> (t == null ? (file.getLastTreeWithoutErrors().get()) : t))
.thenCompose(tr -> rascalServices.getOutline(tr).get())
.thenApply(c -> Outline.buildOutline(c, columns.get(file.getLocation())))
;
Expand All @@ -285,7 +298,7 @@ public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {

return file.getCurrentTreeAsync()
.thenApply(Versioned::get)
.handle((t, r) -> (t == null ? (file.getMostRecentTree().get()) : t))
.handle((t, r) -> (t == null ? (file.getLastTreeWithoutErrors().get()) : t))
.thenCompose(tr -> rascalServices.getRename(tr, params.getPosition(), workspaceFolders, facts::getPathConfig, params.getNewName(), columns).get())
.thenApply(c -> new WorkspaceEdit(DocumentChanges.translateDocumentChanges(this, c)))
;
Expand Down Expand Up @@ -395,7 +408,7 @@ public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams param
.handle((r, e) -> {
// fallback to tree if a parsing error occurred.
if (r == null) {
r = f.getMostRecentTree();
r = f.getLastTreeWithoutErrors();
}
if (r == null) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import org.eclipse.lsp4j.Range;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.values.parsetrees.TreeAdapter;
import org.rascalmpl.vscode.lsp.IBaseTextDocumentService;
import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps;
import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap;
Expand Down Expand Up @@ -74,6 +76,9 @@ public static Diagnostic translateDiagnostic(ParseError e, ColumnMaps cm) {
return new Diagnostic(toRange(e, cm), e.getMessage(), DiagnosticSeverity.Error, "parser");
}

public static Diagnostic translateErrorRecoveryDiagnostic(ITree errorTree, ColumnMaps cm) {
return new Diagnostic(toRange(errorTree, cm), "Parse error", DiagnosticSeverity.Error, "parser");
}

public static Diagnostic translateRascalParseError(IValue e, ColumnMaps cm) {
if (e instanceof IConstructor) {
Expand Down Expand Up @@ -121,14 +126,27 @@ public static Diagnostic translateDiagnostic(IConstructor d, Range range) {
return result;
}

private static Range toRange(ITree t, ColumnMaps cm) {
return toRange(TreeAdapter.getLocation(t), cm);
}

private static Range toRange(ParseError pe, ColumnMaps cm) {
ISourceLocation loc = pe.getLocation();
return toRange(pe.getLocation(), cm);
}

private static Range toRange(ISourceLocation loc, ColumnMaps cm) {
if (loc.getBeginLine() == loc.getEndLine() && loc.getBeginColumn() == loc.getEndColumn()) {
// zero width parse error is not something LSP likes, so we make it one char wider
loc = ValueFactoryFactory.getValueFactory().sourceLocation(loc,
loc.getOffset(), loc.getLength() + 1,
loc.getBeginLine(), loc.getBeginColumn(),
loc.getEndLine(), loc.getEndColumn() + 1);
try {
loc = ValueFactoryFactory.getValueFactory().sourceLocation(loc,
loc.getOffset(), loc.getLength() + 1,
loc.getBeginLine(), loc.getBeginColumn(),
loc.getEndLine(), loc.getEndColumn() + 1);
} catch (Throwable t) {
logger.trace("Cannot extend 0-width location for parse error: " + t.getMessage());
loc = ValueFactoryFactory.getValueFactory().sourceLocation(
loc, 0, 1, 1, 1, 0, 1);
}
}
return Locations.toRange(loc, cm);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,39 @@
package org.rascalmpl.vscode.lsp.util;

import org.rascalmpl.library.lang.rascal.syntax.RascalParser;
import org.rascalmpl.library.util.ErrorRecovery;
import org.rascalmpl.parser.Parser;
import org.rascalmpl.parser.gtd.result.action.IActionExecutor;
import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener;
import org.rascalmpl.parser.gtd.util.StackNodeIdDispenser;
import org.rascalmpl.parser.uptr.UPTRNodeFactory;
import org.rascalmpl.parser.uptr.action.NoActionExecutor;
import org.rascalmpl.parser.uptr.recovery.ToTokenRecoverer;
import org.rascalmpl.values.RascalValueFactory;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.parsetrees.ITree;

import io.usethesource.vallang.IBool;
import io.usethesource.vallang.ISourceLocation;

public class RascalServices {

private static final RascalValueFactory VALUE_FACTORY = (RascalValueFactory) ValueFactoryFactory.getValueFactory();
private static final IBool TRUE = VALUE_FACTORY.bool(true);

public static ITree parseRascalModule(ISourceLocation loc, char[] input) {
IActionExecutor<ITree> actions = new NoActionExecutor();
return new RascalParser().parse(Parser.START_MODULE, loc.getURI(), input, actions,
new DefaultNodeFlattener<>(), new UPTRNodeFactory(true));
// TODO: Which of these objects are stateless and can be reused?

// Parse
RascalParser parser = new RascalParser();
ITree tree = parser.parse(
Parser.START_MODULE, loc.getURI(), input,
new NoActionExecutor(),
new DefaultNodeFlattener<>(),
new UPTRNodeFactory(true),
new ToTokenRecoverer(loc.getURI(), parser, new StackNodeIdDispenser(parser)));

// Recover
ErrorRecovery recoverer = new ErrorRecovery(VALUE_FACTORY);
return (ITree) recoverer.disambiguateErrors(tree, TRUE);
}
}
Loading
Loading