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

Both outline and codelenses support can now handle error trees #491

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
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());
PieterOlivier marked this conversation as resolved.
Show resolved Hide resolved

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
72 changes: 47 additions & 25 deletions rascal-lsp/src/main/rascal/lang/rascal/lsp/Outline.rsc
PieterOlivier marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,58 @@ import String;
import ParseTree;
import lang::rascal::\syntax::Rascal;
import util::LanguageServer;
import util::ErrorRecovery;

list[DocumentSymbol] outlineRascalModule(start[Module] \mod) {
m= \mod.top;
children = [];

top-down-break visit (m) {
case (Declaration) `<Tags _> <Visibility _> <Type t> <{Variable ","}+ vars>;`:
children += [symbol(clean("<v.name>"), variable(), v@\loc, detail="variable <t> <v>") | v <- vars];

case (Declaration) `<Tags _> <Visibility _> anno <Type t> <Type ot>@<Name name>;`:
children += [symbol(clean("<name>"), field(), t@\loc, detail="anno <t> <ot>")];

case (Declaration) `<Tags _> <Visibility _> alias <UserType u> = <Type al>;`:
children += [symbol(clean("<u.name>"), struct(), u@\loc, detail="<u> = <al>")];
if (!(m has header) || hasErrors(m.header)) {
return [];
}

case (Declaration) `<Tags _> <Visibility _> tag <Kind k> <Name name> on <{Type ","}+ ts>;`:
children += [symbol(clean("<name>"), \key(), name@\loc, detail="tag <k> <name> on <ts>")];
children = [];

case (Declaration) `<Tags _> <Visibility _> data <UserType u> <CommonKeywordParameters kws>;`: {
kwlist = [symbol(".<k.name>", property(), k@\loc, detail="<k.\type>") | kws is present, KeywordFormal k <- kws.keywordFormalList];
children += [symbol("<u.name>", struct(), u@\loc, detail="data <u> <kws>", children=kwlist)];
top-down-break visit (m) {
case decl: (Declaration) `<Tags _> <Visibility _> <Type t> <{Variable ","}+ vars>;`:
if (!hasErrors(decl)) {
children += [symbol(clean("<v.name>"), variable(), v@\loc, detail="variable <t> <v>") | v <- vars, !hasErrors(v)];
PieterOlivier marked this conversation as resolved.
Show resolved Hide resolved
}

case decl: (Declaration) `<Tags _> <Visibility _> anno <Type t> <Type ot>@<Name name>;`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once the match succeeds we now this node is not in error, so we don't need to check hasErrors for that.. but...

if (!hasErrors(decl)) {
children += [symbol(clean("<name>"), field(), t@\loc, detail="anno <t> <ot>")];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you see only bound variables are yielded to string, and that is supported by error trees as well, so no problem..

}

case decl: (Declaration) `<Tags _> <Visibility _> alias <UserType u> = <Type al>;`:
if (!hasErrors(decl)) {
children += [symbol(clean("<u.name>"), struct(), u@\loc, detail="<u> = <al>")];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but here we use field projecten u.name and u might not even have a name if UserType u is an error tree. This is going to happen all over the place for error trees, so I'd like a more reusable/generic solution then having to guard all the field projections. It will become a cross-cutting and scattered concern everywhere otherwise, while error trees are a builtin language concept that features like . projection {c,sh,w}ould handle gracefully.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may assume that if a programmer uses u.name then in that place u.name normally does not fail, and so it should also not fail for error children.

  • if u is an error tree, and the error "dot" is after the name field, then field projection can still work as it was supposed to.
  • otherwise if the "dot" is before the name field or on it, then it may be possible to return an empty stub error tree for name instead of failing.

If we have such a semantics for field projection, there would be a lot less pressure on user code for dealing with all kinds of exceptions. In general for productions with n children, there are I guess something like n!/(2 * (n-2)!) possible combinations of having and not having error children, so that can become untenable.

Another option is to rewrite all field projections to pattern matches, which would fail in the absence of a field. But in this example that would lead to a lot of superfluous case distintions:

syntax UserType
	= name: QualifiedName name 
	| parametric: QualifiedName name >> "[" "[" {Type ","}+ parameters "]" ;

Here we really do not care about the difference between the two rules but rather their commonality which is fine by the .name field projection. So I guess pattern matching is not a solution here.

}

case decl: (Declaration) `<Tags _> <Visibility _> tag <Kind k> <Name name> on <{Type ","}+ ts>;`:
if (!hasErrors(decl)) {
children += [symbol(clean("<name>"), \key(), name@\loc, detail="tag <k> <name> on <ts>")];
}

case decl: (Declaration) `<Tags _> <Visibility _> data <UserType u> <CommonKeywordParameters kws>;`: {
if (!hasErrors(decl)) {
kwlist = [symbol(".<k.name>", property(), k@\loc, detail="<k.\type>") | kws is present, KeywordFormal k <- kws.keywordFormalList];
children += [symbol("<u.name>", struct(), u@\loc, detail="data <u> <kws>", children=kwlist)];
}
}

case (Declaration) `<Tags _> <Visibility _> data <UserType u> <CommonKeywordParameters kws> = <{Variant "|"}+ variants>;` : {
kwlist = [symbol(".<k.name>", property(), k@\loc, detail="<k.\type>") | kws is present, KeywordFormal k <- kws.keywordFormalList];
variantlist = [symbol(clean("<v>"), \constructor(), v@\loc) | v <- variants];
case decl: (Declaration) `<Tags _> <Visibility _> data <UserType u> <CommonKeywordParameters kws> = <{Variant "|"}+ variants>;` : {
if (!hasErrors(decl)) {
kwlist = [symbol(".<k.name>", property(), k@\loc, detail="<k.\type>") | kws is present, KeywordFormal k <- kws.keywordFormalList];
variantlist = [symbol(clean("<v>"), \constructor(), v@\loc) | v <- variants];

children += [symbol("<u.name>", struct(), u@\loc, detail="data <u> <kws>", children=kwlist + variantlist)];
children += [symbol("<u.name>", struct(), u@\loc, detail="data <u> <kws>", children=kwlist + variantlist)];
}
}

case FunctionDeclaration func :
children += [symbol("<func.signature.name><func.signature.parameters>", \function(), (func.signature)@\loc, detail="<func.signature.\type>")];
if (!hasErrors(func)) {
children += [symbol("<func.signature.name><func.signature.parameters>", \function(), (func.signature)@\loc, detail="<func.signature.\type>")];
}

/*
case (Import) `extend <ImportedModule mm>;` :
Expand All @@ -76,11 +96,13 @@ list[DocumentSymbol] outlineRascalModule(start[Module] \mod) {
*/

case SyntaxDefinition def : {
rs = [symbol(clean("<prefix> <p.syms>"), \function(), p@\loc)
| /Prod p := def.production, p is labeled || p is unlabeled,
str prefix := (p is labeled ? "<p.name>: " : "")
];
children += [symbol(clean("<def.defined>"), \struct(), def@\loc, children=rs)];
if (!hasErrors(def)) {
rs = [symbol(clean("<prefix> <p.syms>"), \function(), p@\loc)
| /Prod p := def.production, !hasErrors(p) && (p is labeled || p is unlabeled),
str prefix := (p is labeled ? "<p.name>: " : "")
];
children += [symbol(clean("<def.defined>"), \struct(), def@\loc, children=rs)];
}
}
}

Expand Down
Loading