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

Parameterize rename framework #547

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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 @@ -213,7 +213,7 @@ public InterruptibleFuture<ITuple> getRename(ITree module, Position cursor, Set<
return runEvaluator("Rascal rename", semanticEvaluator, eval -> {
try {
IFunction rascalGetPathConfig = eval.getFunctionValueFactory().function(getPathConfigType, (t, u) -> addResources(getPathConfig.apply((ISourceLocation) t[0])));
return (ITuple) eval.call("rascalRenameSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), VF.string(newName), rascalGetPathConfig);
return (ITuple) eval.call("rascalRenameSymbol", cursorTree, VF.string(newName), VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), rascalGetPathConfig);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Swap parameter order to make more sense

} catch (Throw e) {
if (e.getException() instanceof IConstructor) {
var exception = (IConstructor)e.getException();
Expand Down
158 changes: 41 additions & 117 deletions rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,17 @@ import lang::rascal::\syntax::Rascal;

import lang::rascalcore::check::Checker;

extend lang::rascal::lsp::refactor::Exception;
import lang::rascal::lsp::refactor::Util;
import lang::rascal::lsp::refactor::WorkspaceInfo;
extend util::refactor::Exception;
extend util::refactor::Rename;
import util::refactor::TextEdits;
import util::Util;

import lang::rascal::lsp::refactor::TextEdits;
import lang::rascal::lsp::refactor::WorkspaceInfo;

import util::FileSystem;
import util::Maybe;
import util::Monitor;
import util::Reflective;

alias Edits = tuple[list[DocumentEdit], map[ChangeAnnotationId, ChangeAnnotation]];

private str MANDATORY_CHANGE_DESCRIPTION = "These changes are required for a correct renaming. They can be previewed here, but it is not advised to disable them.";

// Rascal compiler-specific extension
void throwAnyErrors(list[ModuleMessages] mmsgs) {
for (mmsg <- mmsgs) {
Expand All @@ -77,9 +73,9 @@ void throwAnyErrors(program(_, msgs)) {
throwAnyErrors(msgs);
}

private set[IllegalRenameReason] rascalCheckLegalName(str name, set[IdRole] roles) {
private set[IllegalRenameReason] rascalCheckLegalNameByRoles(str name, set[IdRole] roles) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed (like rascalCheckLegalNameByType below) to be able to pass as argument.

escName = rascalEscapeName(name);
tuple[type[&T <: Tree] as, str desc] asType = <#Name, "identifier">;
tuple[type[Tree] as, str desc] asType = <#Name, "identifier">;
if ({moduleId(), *_} := roles) asType = <#QualifiedName, "module name">;
if ({constructorId(), *_} := roles) asType = <#NonterminalLabel, "constructor name">;
if ({fieldId(), *_} := roles) asType = <#NonterminalLabel, "constructor field name">;
Expand All @@ -89,12 +85,13 @@ private set[IllegalRenameReason] rascalCheckLegalName(str name, set[IdRole] role
return {};
}

private void rascalCheckLegalName(str name, Symbol sym) {
set[IllegalRenameReason] rascalCheckLegalNameByType(str name, Symbol sym) {
escName = rascalEscapeName(name);
g = grammar(#start[Module]);
if (tryParseAs(type(sym, g.rules), escName) is nothing) {
throw illegalRename("\'<escName>\' is not a valid name at this position", {invalidName(escName, "<sym>")});
if (type[Tree] t := type(sym, g.rules), tryParseAs(t, escName) is nothing) {
return {invalidName(escName, "<sym>")};
}
return {};
}

private set[IllegalRenameReason] rascalCheckDefinitionsOutsideWorkspace(TModel ws, set[loc] defs) =
Expand Down Expand Up @@ -174,17 +171,18 @@ private set[IllegalRenameReason] rascalCollectIllegalRenames(TModel ws, start[Mo
set[Define] newNameDefs = {def | Define def:<_, newName, _, _, _, _> <- ws.defines};

return
rascalCheckLegalName(newName, definitionsRel(ws)[currentDefs].idRole)
rascalCheckLegalNameByRoles(newName, definitionsRel(ws)[currentDefs].idRole)
+ rascalCheckDefinitionsOutsideWorkspace(ws, currentDefs)
+ rascalCheckCausesDoubleDeclarations(ws, currentDefs, newNameDefs, newName)
+ rascalCheckCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs)
;
}

private str rascalEscapeName(str name) = name in getRascalReservedIdentifiers() ? "\\<name>" : name;
str rascalEscapeName(str name) = name in getRascalReservedIdentifiers() ? "\\<name>" : name;

// Find the smallest trees of defined non-terminal type with a source location in `useDefs`
private rel[loc, loc] rascalFindNamesInUseDefs(start[Module] m, set[loc] useDefs) {
rel[loc, loc] rascalFindNamesInUseDefs(loc l, set[loc] useDefs) {
start[Module] m = parseModuleWithSpacesCached(l);
rel[loc, loc] nameOfUseDef = {};
useDefsToDo = useDefs;
visit(m.top) {
Expand All @@ -197,7 +195,7 @@ private rel[loc, loc] rascalFindNamesInUseDefs(start[Module] m, set[loc] useDefs
}

if (useDefsToDo != {}) {
throw unsupportedRename("Rename unsupported", issues={<l, "Cannot find the name for this definition in <m.src.top>."> | l <- useDefsToDo});
throw unsupportedRename("Rename unsupported", issues={<ud, "Cannot find the name for this definition in <m.src.top>."> | ud <- useDefsToDo});
}

return nameOfUseDef;
Expand All @@ -221,33 +219,6 @@ Maybe[loc] rascalLocationOfName(Nonterminal nt) = just(nt.src);
Maybe[loc] rascalLocationOfName(NonterminalLabel l) = just(l.src);
default Maybe[loc] rascalLocationOfName(Tree t) = nothing();

private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(TModel ws, start[Module] m, set[RenameLocation] defs, set[RenameLocation] uses, str name, ChangeAnnotationRegister registerChangeAnnotation) {
if (reasons := rascalCollectIllegalRenames(ws, m, defs.l, uses.l, name), reasons != {}) {
return <reasons, []>;
}

replaceName = rascalEscapeName(name);

rel[loc l, Maybe[ChangeAnnotationId] ann, bool isDef] renames =
{<l, a, true> | <l, a> <- defs}
+ {<l, a, false> | <l, a> <- uses};
rel[loc name, loc useDef] nameOfUseDef = rascalFindNamesInUseDefs(m, renames.l);

ChangeAnnotationId defAnno = registerChangeAnnotation("Definitions", MANDATORY_CHANGE_DESCRIPTION, false);
ChangeAnnotationId useAnno = registerChangeAnnotation("References", MANDATORY_CHANGE_DESCRIPTION, false);

// Note: if the implementer of the rename logic has attached annotations to multiple rename suggestions that have the same
// name location, one will be arbitrarily chosen here. This could mean that a `needsConfirmation` annotation is thrown away.
return <{}, [{just(annotation), *_} := renameOpts.ann
? replace(l, replaceName, annotation = annotation)
: replace(l, replaceName, annotation = any(b <- renameOpts.isDef) ? defAnno : useAnno)
| l <- nameOfUseDef.name
, rel[Maybe[ChangeAnnotationId] ann, bool isDef] renameOpts := renames[nameOfUseDef[l]]]>;
}

private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(TModel ws, loc moduleLoc, set[RenameLocation] defs, set[RenameLocation] uses, str name, ChangeAnnotationRegister registerChangeAnnotation) =
computeTextEdits(ws, parseModuleWithSpacesCached(moduleLoc), defs, uses, name, registerChangeAnnotation);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to parametric util::refactor::Rename

private bool rascalIsFunctionLocalDefs(TModel ws, set[loc] defs) {
for (d <- defs) {
if (Define fun: <_, _, _, _, _, defType(afunc(_, _, _))> <- ws.defines
Expand Down Expand Up @@ -384,7 +355,7 @@ private CursorKind rascalGetCursorKind(TModel ws, loc cursorLoc, str cursorName,
throw unsupportedRename("Could not retrieve information for \'<cursorName>\' at <cursorLoc>.");
}

private Cursor rascalGetCursor(TModel ws, Tree cursorT) {
Cursor rascalGetCursor(TModel ws, Tree cursorT) {
loc cursorLoc = cursorT.src;
str cursorName = "<cursorT>";

Expand Down Expand Up @@ -438,7 +409,7 @@ private Cursor rascalGetCursor(TModel ws, Tree cursorT) {

private set[Tree] rascalNameToEquivalentNames(str name) {
set[Tree] equivs = {t
| T <- {#Name, #Nonterminal, #NonterminalLabel}
| type[Tree] T <- {#Name, #Nonterminal, #NonterminalLabel}
, just(Tree t) := tryParseAs(T, name)
};

Expand All @@ -455,43 +426,40 @@ private bool rascalContainsName(loc l, str name) {
return false;
}

private set[TModel] rascalTModels(set[loc] fs, PathConfig pcfg) {
RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[verbose = false]
[logPathConfig = false];
set[TModel] rascalTModels(set[loc] fs, PathConfig pcfg) {
RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[verbose=false][logPathConfig=false];
ms = rascalTModelForLocs(toList(fs), ccfg, dummy_compile1);

set[TModel] tmodels = {};
for (modName <- ms.moduleLocs) {
<found, tm, ms> = getTModelForModule(modName, ms);
if (!found) throw unexpectedFailure("Cannot read TModel for module \'<modName>\'\n<toString(ms.messages)>");
tmodels += convertTModel2PhysicalLocs(tm);
tmodels += tm;
}
return tmodels;
}

ProjectFiles preloadFiles(set[loc] workspaceFolders, loc cursorLoc) {
ProjectFiles rascalPreloadFiles(Tree cursorT, set[loc] workspaceFolders, PathConfig(loc) _) {
loc cursorLoc = cursorT.src;
return { <
max([f | f <- workspaceFolders, isPrefixOf(f, cursorLoc)]),
true,
cursorLoc.top
> };
}

ProjectFiles allWorkspaceFiles(set[loc] workspaceFolders, str cursorName, bool(loc, str) containsName, PathConfig(loc) getPathConfig) {
return {
ProjectFiles rascalAllWorkspaceFiles(TModel tm, Cursor cur, set[loc] workspaceFolders, PathConfig(loc) getPathConfig) =
rascalIsFunctionLocal(tm, cur) ? {}
: {
// If we do not find any occurrences of the name under the cursor in a module,
// we are not interested in loading the model, but we still want to inform the
// renaming framework about the existence of the file.
<folder, containsName(file, cursorName), file>
<folder, rascalContainsName(file, cur.name), file>
| folder <- workspaceFolders
, PathConfig pcfg := getPathConfig(folder)
, srcFolder <- pcfg.srcs
, file <- find(srcFolder, "rsc")
};
}

set[TModel] tmodelsForProjectFiles(ProjectFiles projectFiles, set[TModel](set[loc], PathConfig) tmodelsForFiles, PathConfig(loc) getPathConfig) =
({} | it + tmodelsForFiles(projectFiles[pf, true], pcfg) | pf <- projectFiles.projectFolder, pcfg := getPathConfig(pf));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to parametric util::refactor::Rename


@synopsis{
Rename the Rascal symbol under the cursor. Renames all related (overloaded) definitions and uses of those definitions.
Expand Down Expand Up @@ -560,67 +528,23 @@ set[TModel] tmodelsForProjectFiles(ProjectFiles projectFiles, set[TModel](set[lo
2. It does not change the semantics of the application.
3. It does not change definitions outside of the current workspace.
}
Edits rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig)
= job("renaming <cursorT> to <newName>", Edits(void(str, int) step) {

step("checking validity of new name", 1);
loc cursorLoc = cursorT.src;
str cursorName = "<cursorT>";

rascalCheckLegalName(newName, typeOf(cursorT));

step("preloading minimal workspace information", 1);
set[TModel] localTmodelsForFiles(ProjectFiles projectFiles) = tmodelsForProjectFiles(projectFiles, rascalTModels, getPathConfig);

TModel ws = loadLocs(tmodel(), preloadFiles(workspaceFolders, cursorLoc), localTmodelsForFiles);

step("analyzing name at cursor", 1);
cur = rascalGetCursor(ws, cursorT);

step("loading required type information", 1);
if (!rascalIsFunctionLocal(ws, cur)) {
ws = loadLocs(ws, allWorkspaceFiles(workspaceFolders, cursorName, rascalContainsName, getPathConfig), localTmodelsForFiles);
public RenameSymbolF rascalRenameSymbol = renameSymbolFramework(
CheckResult(Tree c, str nn, set[loc] _, PathConfig(loc) _) { return rascalCheckLegalNameByType(nn, typeOf(c)); }
, CheckResult(TModel tm, loc moduleLoc, str nn, set[RenameLocation] defs, set[RenameLocation] uses) {
start[Module] m = parseModuleWithSpacesCached(moduleLoc);
return rascalCollectIllegalRenames(tm, m, defs.l, uses.l, nn);
}

step("collecting uses of \'<cursorName>\'", 1);

map[ChangeAnnotationId, ChangeAnnotation] changeAnnotations = ();
ChangeAnnotationRegister registerChangeAnnotation = ChangeAnnotationId(str label, str description, bool needsConfirmation) {
ChangeAnnotationId makeKey(str label, int suffix) = "<label>_<suffix>";

int suffix = 1;
while (makeKey(label, suffix) in changeAnnotations) {
suffix += 1;
}

ChangeAnnotationId id = makeKey(label, suffix);
changeAnnotations[id] = changeAnnotation(label, description, needsConfirmation);

return id;
};

<defs, uses, getRenames> = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName, registerChangeAnnotation, getPathConfig);

rel[loc file, RenameLocation defines] defsPerFile = {<d.l.top, d> | d <- defs};
rel[loc file, RenameLocation uses] usesPerFile = {<u.l.top, u> | u <- uses};

set[loc] \files = defsPerFile.file + usesPerFile.file;

step("checking rename validity", 1);

map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults =
(file: <reasons, edits> | file <- \files, <reasons, edits> := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName, registerChangeAnnotation));

if (reasons := union({moduleResults[file].reasons | file <- moduleResults}), reasons != {}) {
list[str] reasonDescs = toList({describe(r) | r <- reasons});
throw illegalRename("Rename is not valid, because:\n - <intercalate("\n - ", reasonDescs)>", reasons);
, rascalPreloadFiles
, rascalAllWorkspaceFiles
, rascalTModels
, rascalEscapeName
, rascalGetCursor
, DefsUsesRenames(TModel tm, Cursor cur, ChangeAnnotationRegister regChangeAnno, PathConfigF gpcfg) {
return rascalGetDefsUses(tm, cur, rascalMayOverloadSameName, regChangeAnno, gpcfg);
}
, rascalFindNamesInUseDefs
);

list[DocumentEdit] changes = [changed(file, moduleResults[file].edits) | file <- moduleResults];
list[DocumentEdit] renames = [renamed(from, to) | <from, to> <- getRenames(newName)];

return <changes + renames, changeAnnotations>;
}, totalWork = 6);

//// WORKAROUNDS

Expand Down
Loading
Loading