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

[WIP] Renaming: split generic machinery from Rascal implementation #546

Closed
wants to merge 1 commit into from
Closed
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
140 changes: 33 additions & 107 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,18 @@ 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,7 +74,7 @@ void throwAnyErrors(program(_, msgs)) {
throwAnyErrors(msgs);
}

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

private void rascalCheckLegalName(str name, Symbol sym) {
private 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>")});
return {invalidName(escName, "<sym>")};
}
return {};
}

private set[IllegalRenameReason] rascalCheckDefinitionsOutsideWorkspace(TModel ws, set[loc] defs) =
Expand Down Expand Up @@ -174,7 +172,7 @@ 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)
Expand All @@ -184,7 +182,8 @@ private set[IllegalRenameReason] rascalCollectIllegalRenames(TModel ws, start[Mo
private 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) {
private rel[loc, loc] rascalFindNamesInUseDefs(loc l, set[loc] useDefs) {
start[Module] m = parseModuleWithSpacesCached(l);
rel[loc, loc] nameOfUseDef = {};
useDefsToDo = useDefs;
visit(m.top) {
Expand Down Expand Up @@ -221,33 +220,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);

private bool rascalIsFunctionLocalDefs(TModel ws, set[loc] defs) {
for (d <- defs) {
if (Define fun: <_, _, _, _, _, defType(afunc(_, _, _))> <- ws.defines
Expand Down Expand Up @@ -469,30 +441,27 @@ private set[TModel] rascalTModels(set[loc] fs, PathConfig pcfg) {
return tmodels;
}

ProjectFiles preloadFiles(set[loc] workspaceFolders, loc cursorLoc) {
ProjectFiles rascalPreloadFiles(Tree cursorT, set[loc] workspaceFolders, PathConfigF _) {
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) {
ProjectFiles rascalAllWorkspaceFiles(cursor(_, _, cursorName), set[loc] workspaceFolders, str cursorName, PathConfig(loc) getPathConfig) {
return {
// 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, cursorName), 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));

@synopsis{
Rename the Rascal symbol under the cursor. Renames all related (overloaded) definitions and uses of those definitions.
Renaming is not supported for some symbols.
Expand Down Expand Up @@ -560,67 +529,24 @@ 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);
}

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;
Edits rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) {
RenameSymbolF rascalRename = renameSymbolFramework(
CheckResult(Tree c, str nn, set[loc] _, PathConfigF _) { return rascalCheckLegalNameByType(nn, typeOf(c)); }
, CheckResult(TModel tm, loc moduleLoc, str nn, set[RenameLocation] defs, set[RenameLocation] uses) {
return rascalCollectIllegalRenames(tm, parseModuleWithSpacesCached(moduleLoc), defs, uses, nn);
}

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);
}

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);
, rascalPreloadFiles
, rascalAllWorkspaceFiles
, rascalTModels
, rascalEscapeName
, rascalGetCursor
, DefsUsesRenames(TModel tm, Cursor cur, ChangeAnnotationRegister regChangeAnno, PathConfigF gpcfg) {
return rascalGetDefsUses(tm, cur, rascalMayOverloadSameName, regChangeAnno, gpcfg);
}
, rascalFindNamesInUseDefs
);
return rascalRename(cursorT, newName, workspaceFolders, getPathConfig);
}

//// WORKAROUNDS

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ import Relation;

import analysis::typepal::TModel;

import lang::rascalcore::check::ATypeUtils;
import lang::rascalcore::check::Checker;
import lang::rascalcore::check::ATypeBase;
import lang::rascalcore::check::BasicRascalConfig;
import lang::rascalcore::check::RascalConfig;

import lang::rascal::\syntax::Rascal;

import util::Maybe;
import util::Reflective;

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

import List;
import Location;
Expand All @@ -68,69 +70,6 @@ data Cursor
;

alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines);
alias FileRenamesF = rel[loc old, loc new](str newName);
alias RenameLocation = tuple[loc l, Maybe[ChangeAnnotationId] annotation];
alias DefsUsesRenames = tuple[set[RenameLocation] defs, set[RenameLocation] uses, FileRenamesF renames];
alias ProjectFiles = rel[loc projectFolder, bool loadModel, loc file];

// Extend the TModel to include some workspace information.
data TModel (
set[loc] projects = {},
set[loc] sourceFiles = {}
);

set[RenameLocation] annotateLocs(set[loc] locs, Maybe[ChangeAnnotationId] annotationId = nothing()) = {<l, annotationId> | l <- locs};

TModel loadLocs(TModel wsTM, ProjectFiles projectFiles, set[TModel](ProjectFiles projectFiles) tmodelsForFiles) {
for (modTM <- tmodelsForFiles(projectFiles)) {
wsTM = appendTModel(wsTM, modTM);
}

// In addition to data from the TModel, we keep track of which projects/modules we loaded.
wsTM.sourceFiles += projectFiles.file;
wsTM.projects += projectFiles.projectFolder;

return wsTM;
}

TModel appendTModel(TModel to, TModel from) {
try {
throwAnyErrors(from);
} catch set[Message] errors: {
throw unsupportedRename("Cannot rename: some files in workspace have errors.\n<toString(errors)>", issues={<(error.at ? |unknown:///|), error.msg> | error <- errors});
}

to.useDef += from.useDef;
to.defines += from.defines;
to.definitions += from.definitions;
to.facts += from.facts;
to.scopes += from.scopes;
to.paths += from.paths;

return to;
}

loc getProjectFolder(TModel ws, loc l) {
if (project <- ws.projects, isPrefixOf(project, l)) {
return project;
}

throw "Could not find project containing <l>";
}

@memo{maximumSize(1), expireAfter(minutes=5)}
rel[loc, loc] defUse(TModel ws) = invert(ws.useDef);

@memo{maximumSize(1), expireAfter(minutes=5)}
map[AType, set[loc]] factsInvert(TModel ws) = invert(ws.facts);

set[loc] getUses(TModel ws, loc def) = defUse(ws)[def];

set[loc] getUses(TModel ws, set[loc] defs) = defUse(ws)[defs];

set[loc] getDefs(TModel ws, loc use) = ws.useDef[use];

Maybe[AType] getFact(TModel ws, loc l) = l in ws.facts ? just(ws.facts[l]) : nothing();

@memo{maximumSize(1), expireAfter(minutes=5)}
set[loc] getModuleScopes(TModel ws) = invert(ws.scopes)[|global-scope:///|];
Expand Down Expand Up @@ -168,9 +107,6 @@ set[loc] rascalReachableModules(TModel ws, set[loc] froms) {
return {s.top | s <- reachable.modScope};
}

@memo{maximumSize(1), expireAfter(minutes=5)}
rel[loc, Define] definitionsRel(TModel ws) = toRel(ws.definitions);

set[Define] rascalReachableDefs(TModel ws, set[loc] defs) {
rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws);
rel[loc from, loc to] scopes = rascalGetTransitiveReflexiveScopes(ws);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ POSSIBILITY OF SUCH DAMAGE.
module lang::rascal::tests::rename::Annotations

import lang::rascal::tests::rename::TestUtils;
import lang::rascal::lsp::refactor::Exception;
import util::refactor::Exception;

test bool userDefinedAnno() = testRenameOccurrences({0, 1, 2}, "
'x = d();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ POSSIBILITY OF SUCH DAMAGE.
module lang::rascal::tests::rename::Fields

import lang::rascal::tests::rename::TestUtils;
import lang::rascal::lsp::refactor::Exception;
import util::refactor::Exception;

test bool constructorField() = testRenameOccurrences({0, 1, 2}, "
'D oneTwo = d(1, 2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ POSSIBILITY OF SUCH DAMAGE.
module lang::rascal::tests::rename::FormalParameters

import lang::rascal::tests::rename::TestUtils;
import lang::rascal::lsp::refactor::Exception;
import util::refactor::Exception;

test bool outerNestedFunctionParameter() = testRenameOccurrences({0, 3}, "
'int f(int foo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ POSSIBILITY OF SUCH DAMAGE.
module lang::rascal::tests::rename::Functions

import lang::rascal::tests::rename::TestUtils;
import lang::rascal::lsp::refactor::Exception;
import util::refactor::Exception;

test bool nestedFunctionParameter() = testRenameOccurrences({0, 1}, "
'int f(int foo, int baz) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ POSSIBILITY OF SUCH DAMAGE.
module lang::rascal::tests::rename::Performance

import lang::rascal::tests::rename::TestUtils;
import lang::rascal::lsp::refactor::Exception;
import util::refactor::Exception;

import List;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ module lang::rascal::tests::rename::TestUtils

import lang::rascal::lsp::refactor::Rename; // Module under test

import lang::rascal::lsp::refactor::Util;
import util::Util;
import util::refactor::TextEdits;

import IO;
import List;
Expand All @@ -46,7 +47,6 @@ import lang::rascalcore::check::RascalConfig;
import lang::rascalcore::compile::util::Names;

import analysis::diff::edits::ExecuteTextEdits;
import lang::rascal::lsp::refactor::TextEdits;

import util::FileSystem;
import util::Math;
Expand Down
Loading
Loading