-
Notifications
You must be signed in to change notification settings - Fork 9
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
base: main
Are you sure you want to change the base?
Changes from all commits
e8dd7d7
0af306c
7b1b6c7
2dd0f1f
e2138e5
8c11f34
c6335d9
5ba9e60
8a704ae
01e644c
2a02da4
9d8de56
9fb9862
580c61c
47b2a28
22b9c04
92f7a51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed (like |
||
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">; | ||
|
@@ -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) = | ||
|
@@ -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) { | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to parametric |
||
private bool rascalIsFunctionLocalDefs(TModel ws, set[loc] defs) { | ||
for (d <- defs) { | ||
if (Define fun: <_, _, _, _, _, defType(afunc(_, _, _))> <- ws.defines | ||
|
@@ -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>"; | ||
|
||
|
@@ -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) | ||
}; | ||
|
||
|
@@ -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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to parametric |
||
|
||
@synopsis{ | ||
Rename the Rascal symbol under the cursor. Renames all related (overloaded) definitions and uses of those definitions. | ||
|
@@ -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 | ||
|
||
|
There was a problem hiding this comment.
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