From e8957e146e756e7a3c380e3e28ff12ecedf5a23d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 13:04:52 +0100 Subject: [PATCH 01/20] Re-design framework using pipeline structure. --- src/main/rascal/refactor/Rename.rsc | 290 +++++++++++++--------------- 1 file changed, 133 insertions(+), 157 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 646cb24..c34764b 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -29,200 +29,176 @@ module refactor::Rename import refactor::TextEdits; -import Message; -import util::Reflective; +import analysis::typepal::TModel; + import IO; import List; +import Map; +import Message; import Node; +import Set; -data TModel; -data Tree; +import util::Reflective; -data RenameState; + +data Tree; alias RenameResult = tuple[list[DocumentEdit], map[str, ChangeAnnotation], set[Message]]; -data RenameSolver( - RenameResult() run = RenameResult() { throw "Not implemented"; } - , void(loc l, void(RenameState, Tree, RenameSolver) doWork, RenameState state) collectParseTree = void(loc _, void(RenameState, Tree, RenameSolver) _, RenameState _) { throw "Not implemented!"; } - , void(loc l, void(RenameState, TModel, RenameSolver) doWork, RenameState state) collectTModel = void(loc _, void(RenameState, TModel, RenameSolver) _, RenameState _) { throw "Not implemented!"; } - , void(Message) msg = void(Message _) { throw "Not implemented"; } - , void(DocumentEdit) documentEdit = void(DocumentEdit _) { throw "Not implemented"; } - , void(TextEdit) textEdit = void(TextEdit _) { throw "Not implemented"; } - , void(str, ChangeAnnotation) annotation = void(str _, ChangeAnnotation _) { throw "Not implemented"; } - , value(str) readStore = value(str _) { throw "Not implemented"; } - , void(str, value) writeStore = void(str _, value _) { throw "Not implemented"; } -) = rsolver(); - -data RenameConfig - = rconfig( - Tree(loc) parseLoc - , TModel(loc) tmodelForLoc - , bool reportCollectCycles = false - , bool debug = true +data Renamer + = rsolver( + void(Message) msg + , void(DocumentEdit) documentEdit + , void(TextEdit) textEdit + , void(str, ChangeAnnotation) annotation + , value(str) readStore + , void(str, value) writeStore ); -alias TreeTask = tuple[loc file, void(RenameState, Tree, RenameSolver) work, RenameState state]; -alias ModelTask = tuple[loc file, void(RenameState, TModel, RenameSolver) work, RenameState state]; - -str describeState(RenameState s) = "#"; - -@example{ - Consumer implements something like: - ``` - alias RenameRequest = tuple[list[Tree] cursorFocus, str newName]; - - public tuple[list[DocumentEdit], map[str, ChangeAnnotation], set[Message] msgs] rename(RenameRequest request) { - RenameConfig config = rconfig(parseFunc, typeCheckFunc); - RenameSolver solver = newSolverForConfig(config); - initSolver(solver, config, request); - return solver.run(); - } - ``` -} -RenameSolver newSolverForConfig(RenameConfig config) { - RenameSolver solver = rsolver(); - // COLLECT - list[TreeTask] treeTaskQueue = []; - list[tuple[loc, RenameState]] treeTasksDone = []; - solver.collectParseTree = void(loc l, void(RenameState, Tree, RenameSolver) doWork, RenameState state) { - if ( notin treeTasksDone) { - treeTaskQueue += ; - treeTasksDone += ; - } else if (config.reportCollectCycles) { - println("-- Cycle detected: skipping parse tree collection for ()"); +RenameResult rename( + list[Tree] cursor + , str newName + , Tree(loc) parseLoc + , TModel(Tree) tmodelForTree + , set[Define](list[Tree] cursor, TModel(Tree) getTModel, Renamer renamer) findDefinitions + , set[loc](set[Define] defs, Renamer renamer) findCandidateFiles + , void(Define def, str newName, TModel tm, Renamer renamer) rename + , bool(set[Define] defs, Tree, Renamer renamer) skipCandidate = bool(_, _, _) { return false; } + , bool debug = true) { + + // Tree & TModel caching + + @memo{maximumSize(50)} + TModel getTModelCached(Tree t) = tmodelForTree(t); + + @memo{maximumSize(50)} + Tree parseLocCached(loc l) { + // We already have the parse tree of the module under cursor + if (l == cursor[-1].src.top) { + return cursor[-1]; } - }; - list[ModelTask] modelTaskQueue = []; - list[tuple[loc, RenameState]] modelTasksDone = []; - solver.collectTModel = void(loc l, void(RenameState, TModel, RenameSolver) doWork, RenameState state) { - if ( notin modelTasksDone) { - modelTaskQueue += ; - modelTasksDone += ; - } else if (config.reportCollectCycles) { - println("-- Cycle detected: skipping TModel collection for ()"); - } - }; + return parseLoc(l); + } - // REGISTER + // Messages set[Message] messages = {}; - solver.msg = void(Message msg) { + void registerMessage(Message msg) { messages += msg; }; - lrel[loc file, DocumentEdit edit] docEdits = []; - solver.documentEdit = void(DocumentEdit edit) { - loc f = edit has file ? edit.file : edit.from; - docEdits += ; - }; - - solver.textEdit = void(TextEdit edit) { - loc f = edit.range.top; - docEdits += ; - }; - - map[str id, ChangeAnnotation annotation] annotations = (); - solver.annotation = void(str annotationId, ChangeAnnotation annotation) { - if (annotationId in annotations) throw "An annotation with id \'\' already exists!"; - annotations[annotationId] = annotation; - }; - - // STORE - map[str, value] store = (); - solver.readStore = value(str key) { return store[key]; }; - solver.writeStore = void(str key, value val) { - store[key] = val; - }; + // Edits + set[value] editsSeen = {}; + list[DocumentEdit] docEdits = []; - // RUN - map[loc, Tree] treeCache = (); - map[loc, TModel] modelCache = (); - - Tree getTree(loc l) { - if (l notin treeCache) { - treeCache[l] = config.parseLoc(l); - } else if (config.debug) { - println("-- Using cached tree for "); + void checkEdit(te:replace(loc range, _)) { + if (te in editsSeen) { + messages += error("Multiple replace edits for this location.", range); } - return treeCache[l]; - } - TModel getTModel(loc l) { - if (l notin modelCache) { - modelCache[l] = config.tmodelForLoc(l); - } else if (config.debug) { - println("-- Using cached TModel for "); + loc f = range.top; + for (changed(f, _) <- editsSeen) { + messages += error("Multiple replace edits for this location.", range); } - return modelCache[l]; } - solver.run = RenameResult() { - while (treeTaskQueue != [] || modelTaskQueue != []) { - treeTaskQueueCopy = treeTaskQueue; - modelTaskQueueCopy = modelTaskQueue; - - // We will do all tasks in the queue - treeTaskQueue = []; - modelTaskQueue = []; - - for (loc f <- treeTaskQueueCopy.file + modelTaskQueueCopy.file) { - fileTreeTasks = treeTaskQueueCopy[f]; - if (config.debug) println(" tasks for tree of "); - - Tree tree = getTree(f); - for ( <- treeTaskQueueCopy[f]) { - treeWork(state, tree, solver); + void checkEdit(DocumentEdit e) { + if (changed(f, tes) := e) { + // Check contents of DocumentEdit + for (te:replace(range, _) <- tes) { + // Check integrity + if (range.top != f) { + messages += error("Invalid replace edit for this location. This location is not in , for which it was registered.", range); } - fileModelTasks = modelTaskQueueCopy[f]; - if (config.debug) println(" tasks for model of "); - - TModel model = getTModel(f); - for ( <- modelTaskQueueCopy[f]) { - modelWork(state, model, solver); - } + // Check text edits + checkEdit(te); } + } else if (e in editsSeen) { + loc file = e has file ? e.file : e.from; + messages += error("Multiple edits for this file.", file); } + } - // Merge document edits - return ; + void registerDocumentEdit(DocumentEdit e) { + checkEdit(e); + docEdits += e; }; - return solver; -} - -list[DocumentEdit] mergeTextEdits(list[DocumentEdit] edits) { - // Only merge subqequent text edits to the same file. - // Leave all other edits in the order in which they were registered - list[DocumentEdit] mergedEdits = []; - loc runningFile = |unknown:///|; - list[TextEdit] runningEdits = []; + void registerTextEdit(TextEdit e) { + checkEdit(e); - void batchRunningEdits(loc thisFile) { - if (runningEdits != []) { - mergedEdits += changed(runningFile, runningEdits); + loc f = e.range.top; + if ([*_, changed(f, prev)] := docEdits) { + // If possible, merge with latest document edit + docEdits[-1] = changed(f, prev + e); + } else { + // Else, create new document edit + docEdits += changed(f, [e]); } - runningFile = thisFile; - runningEdits = []; - } + }; + + map[str id, ChangeAnnotation annotation] annotations = (); + void registerAnnotation(str annotationId, ChangeAnnotation annotation) { + if (annotationId in annotations) throw "An annotation with id \'\' already exists!"; + annotations[annotationId] = annotation; + }; - for (DocumentEdit e <- edits) { - loc thisFile = e has file ? e.file : e.from; - if (thisFile != runningFile) { - batchRunningEdits(thisFile); + // Store + map[str, value] store = (); + value readStore(str key) { return store[key]; }; + void writeStore(str key, value val) { store[key] = val; }; + + Renamer renamer = rsolver( + registerMessage + , registerDocumentEdit + , registerTextEdit + , registerAnnotation + , readStore + , writeStore + ); + + if (debug) println("Renaming to \'\'"); + + if (debug) println("+ Finding definitions for cursor at "); + set[Define] defs = findDefinitions(cursor, getTModelCached, renamer); + if (debug) println("+ Finding candidate files"); + set[loc] candidates = findCandidateFiles(defs, renamer); + for (loc f <- candidates) { + if (debug) println(" - Processing candidate "); + if (debug) println(" + Retrieving parse tree"); + Tree t = parseLocCached(f); + if (skipCandidate(defs, t, renamer)) { + if (debug) println(" + Skipping"); + continue; } - if (e is changed) { - runningEdits += e.edits; - } else { - batchRunningEdits(thisFile); - mergedEdits += e; + if (debug) println(" + Retrieving TModel"); + TModel tm = getTModelCached(t); + if (debug) println(" + Renaming each definition"); + for (Define d <- defs) { + if (debug) println(" - Renaming \'\'"); + rename(d, newName, tm, renamer); } + if (debug) println(" - Done!"); + } + if (debug) println("+ Done!"); + if (debug) { + println("\n\n============\nRename statistics\n============\n"); + int nDocs = size({f | de <- docEdits, f := (de has file ? de.file : de.from)}); + int nEdits = (0 | it + ((changed(_, tes) := e) ? size(tes) : 1) | e <- docEdits); + + int nErrors = size({msg | msg <- messages, msg is error}); + int nWarnings = size({msg | msg <- messages, msg is warning}); + int nInfos = size({msg | msg <- messages, msg is info}); + + println(" # of documents affected: "); + println(" # of text edits: "); + println(" # of messages: "); + println(" ( errors, warnings and infos)"); + println(" # of annotations: "); } - batchRunningEdits(|unknown:///|); - - return mergedEdits; + return ; } From 0d69ac4949da38b8f2cdea201032641cfa8267ad Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 14:30:24 +0100 Subject: [PATCH 02/20] Fixes & naming. --- src/main/rascal/refactor/Rename.rsc | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index c34764b..6c781ee 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -37,17 +37,15 @@ import List; import Map; import Message; import Node; +import ParseTree; import Set; import util::Reflective; - -data Tree; - alias RenameResult = tuple[list[DocumentEdit], map[str, ChangeAnnotation], set[Message]]; data Renamer - = rsolver( + = renamer( void(Message) msg , void(DocumentEdit) documentEdit , void(TextEdit) textEdit @@ -61,10 +59,10 @@ RenameResult rename( , str newName , Tree(loc) parseLoc , TModel(Tree) tmodelForTree - , set[Define](list[Tree] cursor, TModel(Tree) getTModel, Renamer renamer) findDefinitions - , set[loc](set[Define] defs, Renamer renamer) findCandidateFiles - , void(Define def, str newName, TModel tm, Renamer renamer) rename - , bool(set[Define] defs, Tree, Renamer renamer) skipCandidate = bool(_, _, _) { return false; } + , set[Define](list[Tree] cursor, TModel(Tree) getTModel, Renamer r) findDefinitions + , set[loc](set[Define] defs, Renamer r) findCandidateFiles + , void(Define def, str newName, TModel tm, Renamer r) renameDef + , bool(set[Define] defs, Tree t, Renamer r) skipCandidate = bool(_, _, _) { return false; } , bool debug = true) { // Tree & TModel caching @@ -130,9 +128,11 @@ RenameResult rename( checkEdit(e); loc f = e.range.top; - if ([*_, changed(f, prev)] := docEdits) { + if ([*pre, changed(f, prev)] := docEdits) { // If possible, merge with latest document edit - docEdits[-1] = changed(f, prev + e); + // TODO Just assign to docEdits[-1], once this issue has been solved: + // https://github.com/usethesource/rascal/issues/2123 + docEdits = [*pre, changed(f, prev + e)]; } else { // Else, create new document edit docEdits += changed(f, [e]); @@ -150,7 +150,7 @@ RenameResult rename( value readStore(str key) { return store[key]; }; void writeStore(str key, value val) { store[key] = val; }; - Renamer renamer = rsolver( + Renamer r = renamer( registerMessage , registerDocumentEdit , registerTextEdit @@ -162,14 +162,14 @@ RenameResult rename( if (debug) println("Renaming to \'\'"); if (debug) println("+ Finding definitions for cursor at "); - set[Define] defs = findDefinitions(cursor, getTModelCached, renamer); + set[Define] defs = findDefinitions(cursor, getTModelCached, r); if (debug) println("+ Finding candidate files"); - set[loc] candidates = findCandidateFiles(defs, renamer); + set[loc] candidates = findCandidateFiles(defs, r); for (loc f <- candidates) { if (debug) println(" - Processing candidate "); if (debug) println(" + Retrieving parse tree"); Tree t = parseLocCached(f); - if (skipCandidate(defs, t, renamer)) { + if (skipCandidate(defs, t, r)) { if (debug) println(" + Skipping"); continue; } @@ -179,7 +179,7 @@ RenameResult rename( if (debug) println(" + Renaming each definition"); for (Define d <- defs) { if (debug) println(" - Renaming \'\'"); - rename(d, newName, tm, renamer); + renameDef(d, newName, tm, r); } if (debug) println(" - Done!"); } From 13f33cfebe4d1463933a6d25ae9ea4c7699fedf3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 14:31:06 +0100 Subject: [PATCH 03/20] Rewrite pico renaming for pipeline. --- src/main/rascal/examples/pico/Rename.rsc | 122 ++++--------------- src/main/rascal/examples/pico/RenameTest.rsc | 25 +++- 2 files changed, 46 insertions(+), 101 deletions(-) diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index 1b6b0cb..eb51a2b 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -41,93 +41,46 @@ import util::FileSystem; data Tree; -alias RenameRequest = tuple[list[Tree] cursor, str newName/*, set[loc] workspaceFolders*/]; - -data RenameState - = findDefinition(RenameRequest req) - | rename(IdRole role, loc def, str newName) - ; - -@memo{expireAfter(minutes=1), maximumSize(50)} -Tree parseLoc(loc l) { - return parse(#start[Program], l); -} - -@memo{expireAfter(minutes=1), maximumSize(50)} -TModel tmodelForLoc(loc l) { - return collectAndSolve(parseLoc(l)); -} +public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renamePico(list[Tree] cursor, str newName) { + if (!isValidName(newName)) { + return <[], (), {error("\'\' is not a valid name here.", cursor[0].src)}>; + } -public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renamePico(RenameRequest request) { - RenameConfig config = rconfig( - parseLoc - , tmodelForLoc - , reportCollectCycles = true + return rename( + cursor + , newName + , Tree(loc l) { return parse(#start[Program], l); } + , collectAndSolve + , findDefinitions + , findCandidateFiles + , renameDef ); - - RenameSolver solver = newSolverForConfig(config); - initSolver(solver, request); - return solver.run(); } -void initSolver(RenameSolver solver, RenameRequest req) { - if (!isValidName(req.newName)) { - // gooi errors - solver.msg(error("Not a valid name: \'\'!")); - return; +set[Define] findDefinitions(list[Tree] cursor, TModel(Tree) getTModel, Renamer r) { + TModel tm = getTModel(cursor[-1]); + if (Tree t <- cursor + , tm.definitions[t.src]?) { + return {tm.definitions[t.src]}; } - loc currentModule = req.cursor[0].src.top; - solver.collectTModel(currentModule, renameByModel, findDefinition(req)); -} - -void renameByModel(findDefinition(RenameRequest req), TModel tm, RenameSolver solver) { - println("Finding definition in TModel \'\'"); - - loc fileUnderCursor = req.cursor[0].src.top; - loc def = getDefLocation(tm, req.cursor); - IdRole defRole = tm.definitions[def].idRole; - - // Rename occurrences in this file - solver.collectTModel(fileUnderCursor, renameByModel, rename(defRole, def, req.newName)); - - // Rename occurrences in any other files, if the renaming is not local - // if (!isLocalRename(tm, def)) { - // for (loc m <- possibleDownstreamUsers(solver, req.workspaceFolders, def), m != fileUnderCursor) { - // solver.collectParseTree(m, renameByTree, rename(defRole, def, req.newName)); - // } - // } + r.msg(error("No definition for name under cursor", cursor[0].src)); + return {}; } -void renameByModel(rename(role:variableId(), loc def, str newName), TModel tm, RenameSolver solver) { - println("Renaming occurrences in TModel \'\'"); +set[loc] findCandidateFiles(set[Define] defs, Renamer _) = + {d.defined.top | d <- defs}; +void renameDef(Define def, str newName, TModel tm, Renamer r) { // Register edit for uses of def in this file - for (loc u <- invert(tm.useDef)[def]) { - solver.textEdit(replace(u, newName)); + for (loc u <- invert(tm.useDef)[def.defined]) { + r.textEdit(replace(u, newName)); } // Register edit for definitions in this file - if (tm.definitions[def]? && tm.definitions[def].idRole == role) { - solver.textEdit(replace(def, newName)); - } -} - -default void renameByModel(RenameState state, TModel _, RenameSolver _) { - throw "`renameByModel` not implemented for ``"; + r.textEdit(replace(def.defined, newName)); } -// void renameByTree(downstreamUsers(l, newName), Tree m, RenameSolver solver) { -// println("Processing tree \'\'"); -// // If newName exists in parse tree, call collectTModel -// // Else, do nothing -// } - -// default void renameByTree(RenameState state, Tree _, RenameSolver _) { -// throw "`renameByTree` not implemented for ``"; -// } - - bool isValidName(str name) { try { parse(#Id, name); @@ -136,28 +89,3 @@ bool isValidName(str name) { return false; } } - -loc getDefLocation(TModel tm, list[Tree] focus) { - for (Tree t <- focus) { - if (tm.definitions[t.src]?) { - return tm.definitions[t.src].defined; - } - } - - throw "No definition for any cursor!"; -} - -// bool isLocalRename(TModel tm, loc def) { -// // TODO Optimize -// return false; -// } - -// set[loc] possibleDownstreamUsers(RenameSolver solver, set[loc] workspaceFolders, loc def) { -// set[loc] possibleUsers = {}; -// for (loc wsFolder <- workspaceFolders) { -// for (loc picoFile <- find(wsFolder, "pico")) { -// possibleUsers += picoFile; -// } -// } -// return possibleUsers; -// } diff --git a/src/main/rascal/examples/pico/RenameTest.rsc b/src/main/rascal/examples/pico/RenameTest.rsc index ceecd45..2150d3e 100644 --- a/src/main/rascal/examples/pico/RenameTest.rsc +++ b/src/main/rascal/examples/pico/RenameTest.rsc @@ -27,6 +27,8 @@ POSSIBILITY OF SUCH DAMAGE. module examples::pico::RenameTest import examples::pico::Rename; +import examples::pico::Syntax; + import refactor::TextEdits; import util::LanguageServer; // computeFocusList @@ -34,14 +36,15 @@ import util::LanguageServer; // computeFocusList import IO; import List; import Message; +import ParseTree; import Set; import String; import util::FileSystem; -tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename() { - prog = parseLoc(|lib://typepal/src/examples/pico/fac.pico|); - cursor = computeFocusList(prog, 2, 17); - return renamePico(); +tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename(str newName = "foo", int line = 2, int col = 17) { + prog = parse(#start[Program], |lib://typepal/src/examples/pico/fac.pico|); + cursor = computeFocusList(prog, line, col); + return renamePico(cursor, newName); } test bool doesNotCrash() { @@ -61,3 +64,17 @@ test bool editsHaveLangthOfNameUnderCursor() { } return true; } + +test bool failsWithError() { + if (<_, _, {error(_, _), *_}> := basicRename(col = 26)) { + return true; + } + return false; +} + +test bool invalidName() { + if (<_, _, {error(_, _), *_}> := basicRename(newName = "_foo")) { + return true; + } + return false; +} From 4ad472e83f6de753357634ce5e98a92894ad79fb Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 15:39:25 +0100 Subject: [PATCH 04/20] Rewrite modules renaming for pipeline. --- src/main/rascal/examples/modules/Rename.rsc | 121 +++++++----------- .../rascal/examples/modules/RenameTest.rsc | 8 +- src/main/rascal/examples/pico/Rename.rsc | 2 +- src/main/rascal/refactor/Rename.rsc | 4 +- 4 files changed, 53 insertions(+), 82 deletions(-) diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 45dc366..5f4fdcb 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -39,47 +39,48 @@ import Relation; import util::FileSystem; import util::Maybe; -alias RenameRequest = tuple[list[Tree] cursor, str newName, set[loc] workspaceFolders]; - -@memo{expireAfter(minutes=1), maximumSize(50)} -Tree parseLoc(loc l) { - return parse(#start[Program], l); -} - -@memo{expireAfter(minutes=1), maximumSize(50)} -TModel tmodelForLoc(loc l) { - return collectAndSolve(parseLoc(l)); -} - -public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renameModules(RenameRequest request) { - bool nameIsValid = any(ModuleId _ <- req.cursor) - ? isValidName(moduleId(), req.newName) - : isValidName(structId(), req.newName); +public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { + bool nameIsValid = any(ModuleId _ <- cursor) + ? isValidName(moduleId(), newName) + : isValidName(structId(), newName); if (!nameIsValid) { - return <[], (), {error(request.cursor[0].src, "Invalid name: ")}>; + return <[], (), {error("Invalid name: ", cursor[0].src)}>; } - RenameConfig config = rconfig( - parseLoc - , tmodelForLoc - , reportCollectCycles = true + set[loc] findCandidateFiles(set[Define] defs, Renamer r) = + {*ls | loc wsFolder <- workspaceFolders, ls := find(wsFolder, "modules")}; + + return rename( + cursor + , newName + , Tree(loc l) { return parse(#start[Program], l); } + , collectAndSolve + , findDefinitions(workspaceFolders) + , findCandidateFiles + , renameDef + , skipCandidate = skipCandidate ); +} - RenameSolver renamer = newSolverForConfig(config); +set[Define](list[Tree], Tree(loc), TModel(Tree), Renamer) findDefinitions(set[loc] workspaceFolders) = + set[Define](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { - // Find definition of name under cursor - loc fileUnderCursor = req.cursor[0].src.top; - renamer.collectTModel(fileUnderCursor, renameByModel, findDefinition(req)); + TModel tm = getTModel(cursor[-1]); + if (just(Define def) := findDef(cursor, tm)) { + // Definition lives in this module + return {def}; + } - return renamer.run(); -} + // Definition lives in another module. + if (Tree c <- cursor + && set[loc] defs:{_, *_} := tm.useDef[c.src]) { + return {tm.definitions[d] | d <- defs, tm := getTModel(getTree(d.top))}; + } -data RenameState - = checkCandidate(Define d, RenameRequest req) - | findDefinition(RenameRequest req) - | rename(Define d, RenameRequest req) - ; + r.msg(error("No definition for name under cursor", cursor[0].src)); + return {}; +}; bool tryParse(type[&T <: Tree] tp, str s) { try { @@ -93,16 +94,13 @@ bool tryParse(type[&T <: Tree] tp, str s) { bool isValidName(moduleId(), str name) = tryParse(#ModuleId, name); bool isValidName(structId(), str name) = tryParse(#Id, name); -void renameByTree(checkCandidate(Define d, RenameRequest req), Tree modTree, RenameSolver renamer) { - println("Checking for occurrences of \'\'"); +bool skipCandidate(set[Define] defs, Tree modTree, Renamer r) { // Only if the name of the definition appears in the module, consider it a rename candidate - if (/Tree t := modTree, "" == d.id) { - renamer.collectTModel(modTree.src.top, renameByModel, rename(d, req)); + set[str] names = {d.id | d <- defs}; + if (/Tree t := modTree, "" in names) { + return false; } -} - -default void renameByTree(RenameState state, Tree _, RenameSolver _) { - throw "Not implemented: `renameByTree` for state "; + return true; } Maybe[Define] findDef(list[Tree] cursor, TModel tm) { @@ -115,45 +113,16 @@ Maybe[Define] findDef(list[Tree] cursor, TModel tm) { return nothing(); } -void renameByModel(state:findDefinition(RenameRequest req), TModel tm, RenameSolver renamer) { - println("Looking for defintion in "); - if (just(Define def) := findDef(req.cursor, tm)) { - // Definition lives in this module - // Check for occurrences of the definition name in all other files - for (loc wsFolder <- req.workspaceFolders, loc m <- find(wsFolder, "modules")) { - renamer.collectParseTree(m, renameByTree, checkCandidate(def, req)); - } - } else { - // Definition lives in another module. - // Load that first, and continue from there - if (Tree c <- req.cursor - && set[loc] defs := tm.useDef[c.src] - && defs != {}) { - for (loc d <- defs) { - renamer.collectTModel(d.top, renameByModel, state); - } - } - } -} - -void renameByModel(rename(Define d, RenameRequest req), TModel tm, RenameSolver renamer) { - println("Renaming all references to in "); - +void renameDef(Define def, str newName, TModel tm, Renamer r) { // If the definition lives in this module, rename it - if (tm.definitions[d.defined]?) { - println("++ Definition in this module; renaming "); - renamer.textEdit(replace(d.defined, req.newName)); + if (def in tm.defines) { + r.textEdit(replace(def.defined, newName)); } // Rename any uses of the definition in this module - str id = d.id; - set[IdRole] roles = {d.idRole}; - for (use(id, _, loc occ, _, roles) <- tm.uses) { - println("++ Use in this module; renaming "); - renamer.textEdit(replace(occ, req.newName)); + str id = def.id; + for (use(id, _, loc occ, _, set[IdRole] roles) <- tm.uses + , def.idRole in roles) { + r.textEdit(replace(occ, newName)); } } - -default void renameByModel(RenameState state, TModel _, RenameSolver _) { - throw "Not implemented: `renameByModel` for state "; -} diff --git a/src/main/rascal/examples/modules/RenameTest.rsc b/src/main/rascal/examples/modules/RenameTest.rsc index 8575b92..ea4ada7 100644 --- a/src/main/rascal/examples/modules/RenameTest.rsc +++ b/src/main/rascal/examples/modules/RenameTest.rsc @@ -27,21 +27,23 @@ POSSIBILITY OF SUCH DAMAGE. module examples::modules::RenameTest import examples::modules::Rename; +import examples::modules::Syntax; + import refactor::TextEdits; import util::LanguageServer; // computeFocusList import IO; import List; +import ParseTree; import Set; import String; import util::FileSystem; tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { - prog = parseLoc(|lib://typepal/src/examples/modules/.modules|); + prog = parse(#start[Program], |lib://typepal/src/examples/modules/.modules|); cursor = computeFocusList(prog, line, col); - println("Cursor: <""> at "); - return renameModules(); + return renameModules(cursor, newName, {|lib://typepal/src/examples/modules|}); } test bool localStructName() { diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index eb51a2b..efbf217 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -57,7 +57,7 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes ); } -set[Define] findDefinitions(list[Tree] cursor, TModel(Tree) getTModel, Renamer r) { +set[Define] findDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { TModel tm = getTModel(cursor[-1]); if (Tree t <- cursor , tm.definitions[t.src]?) { diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 6c781ee..94659f2 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -59,7 +59,7 @@ RenameResult rename( , str newName , Tree(loc) parseLoc , TModel(Tree) tmodelForTree - , set[Define](list[Tree] cursor, TModel(Tree) getTModel, Renamer r) findDefinitions + , set[Define](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) findDefinitions , set[loc](set[Define] defs, Renamer r) findCandidateFiles , void(Define def, str newName, TModel tm, Renamer r) renameDef , bool(set[Define] defs, Tree t, Renamer r) skipCandidate = bool(_, _, _) { return false; } @@ -162,7 +162,7 @@ RenameResult rename( if (debug) println("Renaming to \'\'"); if (debug) println("+ Finding definitions for cursor at "); - set[Define] defs = findDefinitions(cursor, getTModelCached, r); + set[Define] defs = findDefinitions(cursor, parseLocCached, getTModelCached, r); if (debug) println("+ Finding candidate files"); set[loc] candidates = findCandidateFiles(defs, r); for (loc f <- candidates) { From e1fa1e5f87d4f4b1d38c48e55cda5121da704fef Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 16:23:01 +0100 Subject: [PATCH 05/20] Improve edit checking and message registration. --- src/main/rascal/refactor/Rename.rsc | 38 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 94659f2..e4957f6 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -52,6 +52,11 @@ data Renamer , void(str, ChangeAnnotation) annotation , value(str) readStore , void(str, value) writeStore + + // Helpers + , void(str, loc) warning + , void(str, loc) info + , void(str, loc) error ); RenameResult rename( @@ -82,41 +87,37 @@ RenameResult rename( // Messages set[Message] messages = {}; - void registerMessage(Message msg) { - messages += msg; - }; + void registerMessage(Message msg) { messages += msg; }; // Edits - set[value] editsSeen = {}; + set[loc] editsSeen = {}; list[DocumentEdit] docEdits = []; - void checkEdit(te:replace(loc range, _)) { - if (te in editsSeen) { - messages += error("Multiple replace edits for this location.", range); - } - - loc f = range.top; - for (changed(f, _) <- editsSeen) { - messages += error("Multiple replace edits for this location.", range); + void checkEdit(replace(loc range, _)) { + if (range in editsSeen) { + registerMessage(error("Multiple replace edits for this location.", range)); } + editsSeen += range; } void checkEdit(DocumentEdit e) { + loc file = e has file ? e.file : e.from; if (changed(f, tes) := e) { // Check contents of DocumentEdit for (te:replace(range, _) <- tes) { // Check integrity if (range.top != f) { - messages += error("Invalid replace edit for this location. This location is not in , for which it was registered.", range); + registerMessage(error("Invalid replace edit for this location. This location is not in , for which it was registered.", range)); } // Check text edits checkEdit(te); } - } else if (e in editsSeen) { - loc file = e has file ? e.file : e.from; - messages += error("Multiple edits for this file.", file); + } else if (file in editsSeen) { + registerMessage(error("Multiple edits for this file.", file)); } + + editsSeen += file; } void registerDocumentEdit(DocumentEdit e) { @@ -141,7 +142,7 @@ RenameResult rename( map[str id, ChangeAnnotation annotation] annotations = (); void registerAnnotation(str annotationId, ChangeAnnotation annotation) { - if (annotationId in annotations) throw "An annotation with id \'\' already exists!"; + if (annotationId in annotations) registerMessage(error("An annotation with id \'\' already exists!")); annotations[annotationId] = annotation; }; @@ -157,6 +158,9 @@ RenameResult rename( , registerAnnotation , readStore , writeStore + , void(str s, loc at) { registerMessage(info(s, at)); } + , void(str s, loc at) { registerMessage(warning(s, at)); } + , void(str s, loc at) { registerMessage(error(s, at)); } ); if (debug) println("Renaming to \'\'"); From d66c893b719e03c2127d27072a762276144d99b4 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 16:47:39 +0100 Subject: [PATCH 06/20] Short-circuit on errors. --- src/main/rascal/refactor/Rename.rsc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index e4957f6..5e9f5ac 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -87,6 +87,7 @@ RenameResult rename( // Messages set[Message] messages = {}; + bool errorReported() = messages != {} && any(m <- messages, m is error); void registerMessage(Message msg) { messages += msg; }; // Edits @@ -167,8 +168,12 @@ RenameResult rename( if (debug) println("+ Finding definitions for cursor at "); set[Define] defs = findDefinitions(cursor, parseLocCached, getTModelCached, r); + if (errorReported()) return ; + if (debug) println("+ Finding candidate files"); set[loc] candidates = findCandidateFiles(defs, r); + if (errorReported()) return ; + for (loc f <- candidates) { if (debug) println(" - Processing candidate "); if (debug) println(" + Retrieving parse tree"); From b651f70ff0f3005e6d0302008a7eb84c664b470b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 16:48:13 +0100 Subject: [PATCH 07/20] Move no-defs error to framework. --- src/main/rascal/examples/modules/Rename.rsc | 1 - src/main/rascal/examples/pico/Rename.rsc | 1 - src/main/rascal/refactor/Rename.rsc | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 5f4fdcb..207a2dc 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -78,7 +78,6 @@ set[Define](list[Tree], Tree(loc), TModel(Tree), Renamer) findDefinitions(set[lo return {tm.definitions[d] | d <- defs, tm := getTModel(getTree(d.top))}; } - r.msg(error("No definition for name under cursor", cursor[0].src)); return {}; }; diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index efbf217..d7bad27 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -64,7 +64,6 @@ set[Define] findDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) g return {tm.definitions[t.src]}; } - r.msg(error("No definition for name under cursor", cursor[0].src)); return {}; } diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 5e9f5ac..e704440 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -168,6 +168,7 @@ RenameResult rename( if (debug) println("+ Finding definitions for cursor at "); set[Define] defs = findDefinitions(cursor, parseLocCached, getTModelCached, r); + if (defs == {}) r.error("No definitions found", cursor[0].src); if (errorReported()) return ; if (debug) println("+ Finding candidate files"); From c1876c759d1791e7ebe9bbeb31675b07efded938 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 16 Jan 2025 17:01:03 +0100 Subject: [PATCH 08/20] Clean up warnings. --- src/main/rascal/examples/modules/Rename.rsc | 12 +++++------- src/main/rascal/examples/pico/Rename.rsc | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 207a2dc..2d2e4df 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -48,7 +48,7 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes return <[], (), {error("Invalid name: ", cursor[0].src)}>; } - set[loc] findCandidateFiles(set[Define] defs, Renamer r) = + set[loc] findCandidateFiles(set[Define] _, Renamer _) = {*ls | loc wsFolder <- workspaceFolders, ls := find(wsFolder, "modules")}; return rename( @@ -56,16 +56,14 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes , newName , Tree(loc l) { return parse(#start[Program], l); } , collectAndSolve - , findDefinitions(workspaceFolders) + , findDefinitions , findCandidateFiles , renameDef , skipCandidate = skipCandidate ); } -set[Define](list[Tree], Tree(loc), TModel(Tree), Renamer) findDefinitions(set[loc] workspaceFolders) = - set[Define](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { - +set[Define] findDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer _) { TModel tm = getTModel(cursor[-1]); if (just(Define def) := findDef(cursor, tm)) { // Definition lives in this module @@ -79,7 +77,7 @@ set[Define](list[Tree], Tree(loc), TModel(Tree), Renamer) findDefinitions(set[lo } return {}; -}; +} bool tryParse(type[&T <: Tree] tp, str s) { try { @@ -93,7 +91,7 @@ bool tryParse(type[&T <: Tree] tp, str s) { bool isValidName(moduleId(), str name) = tryParse(#ModuleId, name); bool isValidName(structId(), str name) = tryParse(#Id, name); -bool skipCandidate(set[Define] defs, Tree modTree, Renamer r) { +bool skipCandidate(set[Define] defs, Tree modTree, Renamer _) { // Only if the name of the definition appears in the module, consider it a rename candidate set[str] names = {d.id | d <- defs}; if (/Tree t := modTree, "" in names) { diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index d7bad27..1c7fc4d 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -57,7 +57,7 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes ); } -set[Define] findDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { +set[Define] findDefinitions(list[Tree] cursor, Tree(loc) _, TModel(Tree) getTModel, Renamer _) { TModel tm = getTModel(cursor[-1]); if (Tree t <- cursor , tm.definitions[t.src]?) { From 2d856a54e6959b2b9165acd0c1a73d073b5f7cbd Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 17 Jan 2025 11:35:51 +0100 Subject: [PATCH 09/20] Introduce config/state ADT. --- src/main/rascal/examples/modules/Rename.rsc | 25 ++++++++------ src/main/rascal/examples/pico/Rename.rsc | 13 +++++--- src/main/rascal/refactor/Rename.rsc | 36 ++++++++++----------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 2d2e4df..4f80eb7 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -39,6 +39,10 @@ import Relation; import util::FileSystem; import util::Maybe; +data RenameConfig( + set[loc] workspaceFolders = {} +); + public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { bool nameIsValid = any(ModuleId _ <- cursor) ? isValidName(moduleId(), newName) @@ -48,18 +52,18 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes return <[], (), {error("Invalid name: ", cursor[0].src)}>; } - set[loc] findCandidateFiles(set[Define] _, Renamer _) = - {*ls | loc wsFolder <- workspaceFolders, ls := find(wsFolder, "modules")}; - return rename( cursor , newName - , Tree(loc l) { return parse(#start[Program], l); } - , collectAndSolve - , findDefinitions - , findCandidateFiles - , renameDef - , skipCandidate = skipCandidate + , rconfig( + Tree(loc l) { return parse(#start[Program], l); } + , collectAndSolve + , findDefinitions + , findCandidateFiles + , renameDef + , skipCandidate = skipCandidate + , workspaceFolders = workspaceFolders + ) ); } @@ -79,6 +83,9 @@ set[Define] findDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) g return {}; } +set[loc] findCandidateFiles(set[Define] _, Renamer r) = + {*ls | loc wsFolder <- r.getConfig().workspaceFolders, ls := find(wsFolder, "modules")}; + bool tryParse(type[&T <: Tree] tp, str s) { try { parse(tp, s); diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index 1c7fc4d..3dbe125 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -49,11 +49,14 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes return rename( cursor , newName - , Tree(loc l) { return parse(#start[Program], l); } - , collectAndSolve - , findDefinitions - , findCandidateFiles - , renameDef + , rconfig( + Tree(loc l) { return parse(#start[Program], l); } + , collectAndSolve + , findDefinitions + , findCandidateFiles + , renameDef + , skipCandidate = bool(_, _, _) { return false; } + ) ); } diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index e704440..96e80cd 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -50,8 +50,7 @@ data Renamer , void(DocumentEdit) documentEdit , void(TextEdit) textEdit , void(str, ChangeAnnotation) annotation - , value(str) readStore - , void(str, value) writeStore + , RenameConfig() getConfig // Helpers , void(str, loc) warning @@ -59,21 +58,26 @@ data Renamer , void(str, loc) error ); -RenameResult rename( - list[Tree] cursor - , str newName - , Tree(loc) parseLoc +data RenameConfig + = rconfig( + Tree(loc) parseLoc , TModel(Tree) tmodelForTree , set[Define](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) findDefinitions , set[loc](set[Define] defs, Renamer r) findCandidateFiles , void(Define def, str newName, TModel tm, Renamer r) renameDef , bool(set[Define] defs, Tree t, Renamer r) skipCandidate = bool(_, _, _) { return false; } + ); + +RenameResult rename( + list[Tree] cursor + , str newName + , RenameConfig config , bool debug = true) { // Tree & TModel caching @memo{maximumSize(50)} - TModel getTModelCached(Tree t) = tmodelForTree(t); + TModel getTModelCached(Tree t) = config.tmodelForTree(t); @memo{maximumSize(50)} Tree parseLocCached(loc l) { @@ -82,7 +86,7 @@ RenameResult rename( return cursor[-1]; } - return parseLoc(l); + return config.parseLoc(l); } // Messages @@ -147,18 +151,12 @@ RenameResult rename( annotations[annotationId] = annotation; }; - // Store - map[str, value] store = (); - value readStore(str key) { return store[key]; }; - void writeStore(str key, value val) { store[key] = val; }; - Renamer r = renamer( registerMessage , registerDocumentEdit , registerTextEdit , registerAnnotation - , readStore - , writeStore + , RenameConfig() { return config; } , void(str s, loc at) { registerMessage(info(s, at)); } , void(str s, loc at) { registerMessage(warning(s, at)); } , void(str s, loc at) { registerMessage(error(s, at)); } @@ -167,19 +165,19 @@ RenameResult rename( if (debug) println("Renaming to \'\'"); if (debug) println("+ Finding definitions for cursor at "); - set[Define] defs = findDefinitions(cursor, parseLocCached, getTModelCached, r); + set[Define] defs = config.findDefinitions(cursor, parseLocCached, getTModelCached, r); if (defs == {}) r.error("No definitions found", cursor[0].src); if (errorReported()) return ; if (debug) println("+ Finding candidate files"); - set[loc] candidates = findCandidateFiles(defs, r); + set[loc] candidates = config.findCandidateFiles(defs, r); if (errorReported()) return ; for (loc f <- candidates) { if (debug) println(" - Processing candidate "); if (debug) println(" + Retrieving parse tree"); Tree t = parseLocCached(f); - if (skipCandidate(defs, t, r)) { + if (config.skipCandidate(defs, t, r)) { if (debug) println(" + Skipping"); continue; } @@ -189,7 +187,7 @@ RenameResult rename( if (debug) println(" + Renaming each definition"); for (Define d <- defs) { if (debug) println(" - Renaming \'\'"); - renameDef(d, newName, tm, r); + config.renameDef(d, newName, tm, r); } if (debug) println(" - Done!"); } From f63742d0fac6f6f5a6cead35b7bf5a57057b704f Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 17 Jan 2025 11:36:12 +0100 Subject: [PATCH 10/20] Prevent pretty-printing all trees. --- src/main/rascal/examples/modules/Rename.rsc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 4f80eb7..9cffee6 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -101,10 +101,8 @@ bool isValidName(structId(), str name) = tryParse(#Id, name); bool skipCandidate(set[Define] defs, Tree modTree, Renamer _) { // Only if the name of the definition appears in the module, consider it a rename candidate set[str] names = {d.id | d <- defs}; - if (/Tree t := modTree, "" in names) { - return false; - } - return true; + return !(any(/Id t := modTree, "" in names) + || any(/ModuleId t := modTree, "" in names)); } Maybe[Define] findDef(list[Tree] cursor, TModel tm) { From 5b8e3e9491c94f3158c0c93ee183e48950b8b4af Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 21 Jan 2025 10:55:21 +0100 Subject: [PATCH 11/20] Check all trees and find def/use candidates early. --- src/main/rascal/examples/modules/Rename.rsc | 85 ++++++++++++------- .../rascal/examples/modules/RenameTest.rsc | 18 +++- src/main/rascal/examples/pico/Rename.rsc | 25 +++--- src/main/rascal/refactor/Rename.rsc | 26 ++++-- 4 files changed, 97 insertions(+), 57 deletions(-) diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 9cffee6..7db4a44 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -35,7 +35,9 @@ import refactor::TextEdits; import Exception; import IO; +import Map; import Relation; +import Set; import util::FileSystem; import util::Maybe; @@ -58,33 +60,62 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes , rconfig( Tree(loc l) { return parse(#start[Program], l); } , collectAndSolve - , findDefinitions - , findCandidateFiles + , findCandidates , renameDef + , renameUses , skipCandidate = skipCandidate , workspaceFolders = workspaceFolders ) ); } -set[Define] findDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer _) { - TModel tm = getTModel(cursor[-1]); - if (just(Define def) := findDef(cursor, tm)) { - // Definition lives in this module - return {def}; - } +tuple[set[Define], set[loc]] findCandidates(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { + str cursorName = ""; - // Definition lives in another module. - if (Tree c <- cursor - && set[loc] defs:{_, *_} := tm.useDef[c.src]) { - return {tm.definitions[d] | d <- defs, tm := getTModel(getTree(d.top))}; - } + set[Define] defs = {}; + set[loc] uses = {}; - return {}; -} + for (loc wsFolder <- r.getConfig().workspaceFolders + , loc f <- find(wsFolder, "modules")) { + Tree t = getTree(f); -set[loc] findCandidateFiles(set[Define] _, Renamer r) = - {*ls | loc wsFolder <- r.getConfig().workspaceFolders, ls := find(wsFolder, "modules")}; + Maybe[TModel] tmCache = nothing(); + TModel getLocalTModel() { + if (tmCache is nothing) { + tmCache = just(getTModel(t)); + } + return tmCache.val; + } + + visit (t) { + case (Import) `import `: { + if ("" == cursorName) { + uses += id.src; + } + } + case (DeclInStruct) ``: { + if ("" == cursorName) { + uses += ty.src; + } + } + case s:(TopLevelDecl) `struct { }`: { + if ("" == cursorName + && tm := getLocalTModel() + && Define d:<_, _, _, structId(), _, _> := tm.definitions[s.src]) { + defs += d; + } + } + case m:(Program) `module `: { + if ("" == cursorName + && tm := getLocalTModel() + && Define d:<_, _, _, moduleId(), _, _> := tm.definitions[m.src]) { + defs += d; + } + } + } + } + return ; +} bool tryParse(type[&T <: Tree] tp, str s) { try { @@ -105,26 +136,14 @@ bool skipCandidate(set[Define] defs, Tree modTree, Renamer _) { || any(/ModuleId t := modTree, "" in names)); } -Maybe[Define] findDef(list[Tree] cursor, TModel tm) { - for (Tree t <- cursor) { - if (tm.definitions[t.src]?) { - return just(tm.definitions[t.src]); - } - } - - return nothing(); -} - void renameDef(Define def, str newName, TModel tm, Renamer r) { - // If the definition lives in this module, rename it if (def in tm.defines) { r.textEdit(replace(def.defined, newName)); } +} - // Rename any uses of the definition in this module - str id = def.id; - for (use(id, _, loc occ, _, set[IdRole] roles) <- tm.uses - , def.idRole in roles) { - r.textEdit(replace(occ, newName)); +void renameUses(Define _, str newName, set[loc] useCandidates, TModel _, Renamer r) { + for (loc u <- useCandidates) { + r.textEdit(replace(u, newName)); } } diff --git a/src/main/rascal/examples/modules/RenameTest.rsc b/src/main/rascal/examples/modules/RenameTest.rsc index ea4ada7..f21e4d4 100644 --- a/src/main/rascal/examples/modules/RenameTest.rsc +++ b/src/main/rascal/examples/modules/RenameTest.rsc @@ -46,21 +46,33 @@ tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] m return renameModules(cursor, newName, {|lib://typepal/src/examples/modules|}); } +void checkNoErrors(set[Message] msgs) { + if (m <- msgs, m is error) { + throw "Renaming threw errors:\n - "; + } +} + test bool localStructName() { - = basicRename("C", 6, 9); + = basicRename("C", 6, 9); + + checkNoErrors(msgs); return size(edits) == 1 && size(edits[0].edits) == 1; } test bool importedStructName() { - = basicRename("B", 3, 9); + = basicRename("C", 8, 1); + + checkNoErrors(msgs); return size(edits) == 2 && size(edits[0].edits) == 1 && size(edits[1].edits) == 1; } test bool moduleName() { - = basicRename("A", 1, 9); + = basicRename("A", 1, 8); + + checkNoErrors(msgs); return size(edits) == 2 && size(edits[0].edits) == 1 && size(edits[1].edits) == 1; diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index 3dbe125..79e2ae6 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -52,35 +52,36 @@ public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Mes , rconfig( Tree(loc l) { return parse(#start[Program], l); } , collectAndSolve - , findDefinitions - , findCandidateFiles + , findCandidates , renameDef + , renameUses , skipCandidate = bool(_, _, _) { return false; } ) ); } -set[Define] findDefinitions(list[Tree] cursor, Tree(loc) _, TModel(Tree) getTModel, Renamer _) { +tuple[set[Define], set[loc]] findCandidates(list[Tree] cursor, Tree(loc) _, TModel(Tree) getTModel, Renamer _) { TModel tm = getTModel(cursor[-1]); if (Tree t <- cursor , tm.definitions[t.src]?) { - return {tm.definitions[t.src]}; + set[Define] defs = {tm.definitions[t.src]}; + set[loc] uses = invert(tm.useDef)[defs.defined]; + return ; } - return {}; + return <{}, {}>; } -set[loc] findCandidateFiles(set[Define] defs, Renamer _) = - {d.defined.top | d <- defs}; - void renameDef(Define def, str newName, TModel tm, Renamer r) { + // Register edit for definitions in this file + r.textEdit(replace(def.defined, newName)); +} + +void renameUses(Define def, str newName, set[loc] candidates, TModel tm, Renamer r) { // Register edit for uses of def in this file - for (loc u <- invert(tm.useDef)[def.defined]) { + for (loc u <- candidates) { r.textEdit(replace(u, newName)); } - - // Register edit for definitions in this file - r.textEdit(replace(def.defined, newName)); } bool isValidName(str name) { diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 96e80cd..14adebf 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -62,9 +62,9 @@ data RenameConfig = rconfig( Tree(loc) parseLoc , TModel(Tree) tmodelForTree - , set[Define](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) findDefinitions - , set[loc](set[Define] defs, Renamer r) findCandidateFiles + , tuple[set[Define] defs, set[loc] uses](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) findCandidates , void(Define def, str newName, TModel tm, Renamer r) renameDef + , void(Define def, str newName, set[loc] candidates, TModel tm, Renamer r) renameUses , bool(set[Define] defs, Tree t, Renamer r) skipCandidate = bool(_, _, _) { return false; } ); @@ -164,17 +164,16 @@ RenameResult rename( if (debug) println("Renaming to \'\'"); - if (debug) println("+ Finding definitions for cursor at "); - set[Define] defs = config.findDefinitions(cursor, parseLocCached, getTModelCached, r); + if (debug) println("+ Finding rename candidates for cursor at "); + = config.findCandidates(cursor, parseLocCached, getTModelCached, r); if (defs == {}) r.error("No definitions found", cursor[0].src); if (errorReported()) return ; - if (debug) println("+ Finding candidate files"); - set[loc] candidates = config.findCandidateFiles(defs, r); - if (errorReported()) return ; - + set[loc] candidates = {l.top | l <- uses} + {d.defined.top | d <- defs}; for (loc f <- candidates) { if (debug) println(" - Processing candidate "); + set[loc] fileUses = {u | u <- uses, u.top == f}; + if (debug) println(" + Retrieving parse tree"); Tree t = parseLocCached(f); if (config.skipCandidate(defs, t, r)) { @@ -188,12 +187,13 @@ RenameResult rename( for (Define d <- defs) { if (debug) println(" - Renaming \'\'"); config.renameDef(d, newName, tm, r); + config.renameUses(d, newName, fileUses, tm, r); } if (debug) println(" - Done!"); } if (debug) println("+ Done!"); if (debug) { - println("\n\n============\nRename statistics\n============\n"); + println("\n\n=================\nRename statistics\n=================\n"); int nDocs = size({f | de <- docEdits, f := (de has file ? de.file : de.from)}); int nEdits = (0 | it + ((changed(_, tes) := e) ? size(tes) : 1) | e <- docEdits); @@ -206,6 +206,14 @@ RenameResult rename( println(" # of messages: "); println(" ( errors, warnings and infos)"); println(" # of annotations: "); + + if (size(messages) > 0) { + println("\n===============\nMessages\n==============="); + for (msg <- messages) { + println(" ** "); + } + println(); + } } return ; From b0392c648dbe15523f297c10b41a16eeb48e7a3b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 21 Jan 2025 14:57:44 +0100 Subject: [PATCH 12/20] Implement renaming of overloads. --- src/main/rascal/examples/modfun/A.mfun | 4 + src/main/rascal/examples/modfun/B.mfun | 4 + src/main/rascal/examples/modfun/Rename.rsc | 153 ++++++++++++++++++ .../rascal/examples/modfun/RenameTest.rsc | 81 ++++++++++ src/main/rascal/examples/modules/Rename.rsc | 2 +- src/main/rascal/examples/pico/Rename.rsc | 2 +- src/main/rascal/refactor/Rename.rsc | 9 +- 7 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 src/main/rascal/examples/modfun/A.mfun create mode 100644 src/main/rascal/examples/modfun/B.mfun create mode 100644 src/main/rascal/examples/modfun/Rename.rsc create mode 100644 src/main/rascal/examples/modfun/RenameTest.rsc diff --git a/src/main/rascal/examples/modfun/A.mfun b/src/main/rascal/examples/modfun/A.mfun new file mode 100644 index 0000000..1bffe06 --- /dev/null +++ b/src/main/rascal/examples/modfun/A.mfun @@ -0,0 +1,4 @@ +module A { + def x : int -> int = fun a : int { 5 + a }; + def x : int -> int = fun a : int { 6 + a }; +} diff --git a/src/main/rascal/examples/modfun/B.mfun b/src/main/rascal/examples/modfun/B.mfun new file mode 100644 index 0000000..4e8517e --- /dev/null +++ b/src/main/rascal/examples/modfun/B.mfun @@ -0,0 +1,4 @@ +module B { + import A; + def i : int = x(1); +} \ No newline at end of file diff --git a/src/main/rascal/examples/modfun/Rename.rsc b/src/main/rascal/examples/modfun/Rename.rsc new file mode 100644 index 0000000..af52440 --- /dev/null +++ b/src/main/rascal/examples/modfun/Rename.rsc @@ -0,0 +1,153 @@ +@license{ +Copyright (c) 2024-2025, Swat.engineering +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +} + +module examples::modfun::Rename + +import examples::modfun::Checker; +import examples::modfun::Syntax; + +import refactor::Rename; +import refactor::TextEdits; + +import Exception; +import IO; +import List; +import Map; +import Relation; +import util::FileSystem; +import util::Maybe; + +data RenameConfig( + set[loc] workspaceFolders = {} +); + +public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { + bool nameIsValid = any(ModId _ <- cursor) + ? isValidName(moduleId(), newName) + : isValidName(variableId(), newName); + + if (!nameIsValid) { + return <[], (), {error("Invalid name: ", cursor[0].src)}>; + } + + return rename( + cursor + , newName + , rconfig( + Tree(loc l) { return parse(#start[ModFun], l); } + , TModel(Tree pt) { return collectAndSolve(pt, config = tconfig(mayOverload = bool(set[loc] defs, map[loc, Define] defines) { return true; })); } + , findCandidates + , renameDef + , renameUses + , skipCandidate = skipCandidate + , workspaceFolders = workspaceFolders + ) + ); +} + +tuple[set[Define], set[loc]] findCandidates(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { + set[Define] defs = {}; + set[loc] uses = {}; + str cursorName = ""; + + for (loc wsFolder <- r.getConfig().workspaceFolders + , loc f <- find(wsFolder, "mfun")) { + Tree t = getTree(f); + + Maybe[TModel] tmodelCache = nothing(); + TModel getLocalTModel() { + if (tmodelCache is nothing) { + tmodelCache = just(getTModel(t)); + } + return tmodelCache.val; + } + + visit(t) { + case (ModuleDecl) `module { }`: { + if ("" == cursorName) { + defs += getLocalTModel().definitions[id.src]; + } + } + case (ImportDecl) `import ;`: { + if ("" == cursorName) { + uses += id.src; + } + } + case (VarDecl) `def : = ;`: { + if ("" == cursorName) { + defs += getLocalTModel().definitions[id.src]; + } + } + case (Expression) ``: { + if ("" == cursorName) { + uses += id.src; + } + } + } + } + + return ; +} + +set[loc] findCandidateFiles(set[loc] workspaceFolders) = + {*ls | loc wsFolder <- workspaceFolders, ls := find(wsFolder, "modules")}; + +bool tryParse(type[&T <: Tree] tp, str s) { + try { + parse(tp, s); + return true; + } catch ParseError(_): { + return false; + } +} + +bool isValidName(moduleId(), str name) = tryParse(#ModId, name); +bool isValidName(variableId(), str name) = tryParse(#Id, name); + +bool skipCandidate(set[Define] defs, Tree modTree, Renamer _) { + // Only if the name of the definition appears in the module, consider it a rename candidate + set[str] names = {d.id | d <- defs}; + return !(any(/Id t := modTree, "" in names) + || any(/ModId t := modTree, "" in names)); +} + +void renameDef(Define def, str newName, TModel tm, Renamer r) { + if (def.defined in tm.defines.defined) { + r.textEdit(replace(def.defined, newName)); + } +} + +void renameUses(set[Define] defs, str newName, set[loc] useCandidates, TModel tm, Renamer r) { + set[loc] uses = {occ + | , + use(id, orgId, loc occ, _, {idRole, *_})> + <- defs * toSet(tm.uses) + }; + for (loc u <- useCandidates & uses) { + r.textEdit(replace(u, newName)); + } +} diff --git a/src/main/rascal/examples/modfun/RenameTest.rsc b/src/main/rascal/examples/modfun/RenameTest.rsc new file mode 100644 index 0000000..ca424a6 --- /dev/null +++ b/src/main/rascal/examples/modfun/RenameTest.rsc @@ -0,0 +1,81 @@ +@license{ +Copyright (c) 2024-2025, Swat.engineering +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +} +module examples::modfun::RenameTest + +import examples::modfun::Rename; +import examples::modfun::Syntax; + +import refactor::TextEdits; + +import util::LanguageServer; // computeFocusList + +import IO; +import List; +import ParseTree; +import Set; +import String; +import util::FileSystem; + +tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { + prog = parse(#start[ModFun], |project://rename-framework/src/main/rascal/examples/modfun/.mfun|); + cursor = computeFocusList(prog, line, col); + return renameModules(cursor, newName, {|project://rename-framework/src/main/rascal/examples/modfun/|}); +} + +void checkNoErrors(set[Message] msgs) { + if (m <- msgs, m is error) { + throw "Renaming threw errors:\n - "; + } +} + +test bool moduleName() { + = basicRename("A", 1, 8, newName = "B"); + + checkNoErrors(msgs); + iprintln(edits); + return size(edits) == 2 + && size(edits[0].edits) == 1 + && size(edits[1].edits) == 1; +} + +test bool overloadedFunc() { + = basicRename("A", 2, 9); + + checkNoErrors(msgs); + return size(edits) == 2 + && size(edits[0].edits) == 1 + && size(edits[1].edits) == 2; +} + +test bool importedFunc() { + = basicRename("B", 3, 19); + + checkNoErrors(msgs); + return size(edits) == 2 + && size(edits[0].edits) == 1 + && size(edits[1].edits) == 2; +} diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 7db4a44..779263d 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -142,7 +142,7 @@ void renameDef(Define def, str newName, TModel tm, Renamer r) { } } -void renameUses(Define _, str newName, set[loc] useCandidates, TModel _, Renamer r) { +void renameUses(set[Define] _, str newName, set[loc] useCandidates, TModel _, Renamer r) { for (loc u <- useCandidates) { r.textEdit(replace(u, newName)); } diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index 79e2ae6..7aa3ac0 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -77,7 +77,7 @@ void renameDef(Define def, str newName, TModel tm, Renamer r) { r.textEdit(replace(def.defined, newName)); } -void renameUses(Define def, str newName, set[loc] candidates, TModel tm, Renamer r) { +void renameUses(set[Define] _, str newName, set[loc] candidates, TModel tm, Renamer r) { // Register edit for uses of def in this file for (loc u <- candidates) { r.textEdit(replace(u, newName)); diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 14adebf..31054bb 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -64,7 +64,7 @@ data RenameConfig , TModel(Tree) tmodelForTree , tuple[set[Define] defs, set[loc] uses](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) findCandidates , void(Define def, str newName, TModel tm, Renamer r) renameDef - , void(Define def, str newName, set[loc] candidates, TModel tm, Renamer r) renameUses + , void(set[Define] defs, str newName, set[loc] candidates, TModel tm, Renamer r) renameUses , bool(set[Define] defs, Tree t, Renamer r) skipCandidate = bool(_, _, _) { return false; } ); @@ -185,9 +185,12 @@ RenameResult rename( TModel tm = getTModelCached(t); if (debug) println(" + Renaming each definition"); for (Define d <- defs) { - if (debug) println(" - Renaming \'\'"); + if (debug) println(" - Renaming \'\' @ "); config.renameDef(d, newName, tm, r); - config.renameUses(d, newName, fileUses, tm, r); + } + if (fileUses != {}) { + if (debug) println(" - Renaming uses @ "); + config.renameUses(defs, newName, fileUses, tm, r); } if (debug) println(" - Done!"); } From 6dd6f85bac2a29c81264669e44bf9df2be4d0fe7 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 21 Jan 2025 16:16:21 +0100 Subject: [PATCH 13/20] Remove change annotations from framework. --- src/main/rascal/examples/modfun/Rename.rsc | 4 ++-- src/main/rascal/examples/modfun/RenameTest.rsc | 8 ++++---- src/main/rascal/examples/modules/Rename.rsc | 4 ++-- src/main/rascal/examples/modules/RenameTest.rsc | 12 ++++++------ src/main/rascal/examples/pico/Rename.rsc | 4 ++-- src/main/rascal/examples/pico/RenameTest.rsc | 12 ++++++------ src/main/rascal/refactor/Rename.rsc | 15 +++------------ src/main/rascal/refactor/TextEdits.rsc | 5 ----- 8 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/main/rascal/examples/modfun/Rename.rsc b/src/main/rascal/examples/modfun/Rename.rsc index af52440..8829d80 100644 --- a/src/main/rascal/examples/modfun/Rename.rsc +++ b/src/main/rascal/examples/modfun/Rename.rsc @@ -45,13 +45,13 @@ data RenameConfig( set[loc] workspaceFolders = {} ); -public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { +public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { bool nameIsValid = any(ModId _ <- cursor) ? isValidName(moduleId(), newName) : isValidName(variableId(), newName); if (!nameIsValid) { - return <[], (), {error("Invalid name: ", cursor[0].src)}>; + return <[], {error("Invalid name: ", cursor[0].src)}>; } return rename( diff --git a/src/main/rascal/examples/modfun/RenameTest.rsc b/src/main/rascal/examples/modfun/RenameTest.rsc index ca424a6..2299618 100644 --- a/src/main/rascal/examples/modfun/RenameTest.rsc +++ b/src/main/rascal/examples/modfun/RenameTest.rsc @@ -40,7 +40,7 @@ import Set; import String; import util::FileSystem; -tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { +tuple[list[DocumentEdit] edits, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { prog = parse(#start[ModFun], |project://rename-framework/src/main/rascal/examples/modfun/.mfun|); cursor = computeFocusList(prog, line, col); return renameModules(cursor, newName, {|project://rename-framework/src/main/rascal/examples/modfun/|}); @@ -53,7 +53,7 @@ void checkNoErrors(set[Message] msgs) { } test bool moduleName() { - = basicRename("A", 1, 8, newName = "B"); + = basicRename("A", 1, 8, newName = "B"); checkNoErrors(msgs); iprintln(edits); @@ -63,7 +63,7 @@ test bool moduleName() { } test bool overloadedFunc() { - = basicRename("A", 2, 9); + = basicRename("A", 2, 9); checkNoErrors(msgs); return size(edits) == 2 @@ -72,7 +72,7 @@ test bool overloadedFunc() { } test bool importedFunc() { - = basicRename("B", 3, 19); + = basicRename("B", 3, 19); checkNoErrors(msgs); return size(edits) == 2 diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 779263d..1d6796a 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -45,13 +45,13 @@ data RenameConfig( set[loc] workspaceFolders = {} ); -public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { +public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { bool nameIsValid = any(ModuleId _ <- cursor) ? isValidName(moduleId(), newName) : isValidName(structId(), newName); if (!nameIsValid) { - return <[], (), {error("Invalid name: ", cursor[0].src)}>; + return <[], {error("Invalid name: ", cursor[0].src)}>; } return rename( diff --git a/src/main/rascal/examples/modules/RenameTest.rsc b/src/main/rascal/examples/modules/RenameTest.rsc index f21e4d4..231c462 100644 --- a/src/main/rascal/examples/modules/RenameTest.rsc +++ b/src/main/rascal/examples/modules/RenameTest.rsc @@ -40,7 +40,7 @@ import Set; import String; import util::FileSystem; -tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { +tuple[list[DocumentEdit] edits, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { prog = parse(#start[Program], |lib://typepal/src/examples/modules/.modules|); cursor = computeFocusList(prog, line, col); return renameModules(cursor, newName, {|lib://typepal/src/examples/modules|}); @@ -53,7 +53,7 @@ void checkNoErrors(set[Message] msgs) { } test bool localStructName() { - = basicRename("C", 6, 9); + = basicRename("C", 6, 9); checkNoErrors(msgs); return size(edits) == 1 @@ -61,7 +61,7 @@ test bool localStructName() { } test bool importedStructName() { - = basicRename("C", 8, 1); + = basicRename("C", 8, 1); checkNoErrors(msgs); return size(edits) == 2 @@ -70,7 +70,7 @@ test bool importedStructName() { } test bool moduleName() { - = basicRename("A", 1, 8); + = basicRename("A", 1, 8); checkNoErrors(msgs); return size(edits) == 2 @@ -79,12 +79,12 @@ test bool moduleName() { } // test bool hasFiveChanges() { -// = basicRename(); +// = basicRename(); // return size(edits) == 1 && size(edits[0].edits) == 5; // } // test bool editsHaveLangthOfNameUnderCursor() { -// = basicRename(); +// = basicRename(); // for (changed(_, rs) <- edits, replace(loc l, _) <- rs) { // if (size("output") != l.length) return false; // } diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index 7aa3ac0..dd86283 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -41,9 +41,9 @@ import util::FileSystem; data Tree; -public tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] renamePico(list[Tree] cursor, str newName) { +public tuple[list[DocumentEdit] edits, set[Message] msgs] renamePico(list[Tree] cursor, str newName) { if (!isValidName(newName)) { - return <[], (), {error("\'\' is not a valid name here.", cursor[0].src)}>; + return <[], {error("\'\' is not a valid name here.", cursor[0].src)}>; } return rename( diff --git a/src/main/rascal/examples/pico/RenameTest.rsc b/src/main/rascal/examples/pico/RenameTest.rsc index 2150d3e..fd82ce6 100644 --- a/src/main/rascal/examples/pico/RenameTest.rsc +++ b/src/main/rascal/examples/pico/RenameTest.rsc @@ -41,24 +41,24 @@ import Set; import String; import util::FileSystem; -tuple[list[DocumentEdit] edits, map[str, ChangeAnnotation] annos, set[Message] msgs] basicRename(str newName = "foo", int line = 2, int col = 17) { +tuple[list[DocumentEdit] edits, set[Message] msgs] basicRename(str newName = "foo", int line = 2, int col = 17) { prog = parse(#start[Program], |lib://typepal/src/examples/pico/fac.pico|); cursor = computeFocusList(prog, line, col); return renamePico(cursor, newName); } test bool doesNotCrash() { - = basicRename(); + = basicRename(); return true; } test bool hasFiveChanges() { - = basicRename(); + = basicRename(); return size(edits) == 1 && size(edits[0].edits) == 5; } test bool editsHaveLangthOfNameUnderCursor() { - = basicRename(); + = basicRename(); for (changed(_, rs) <- edits, replace(loc l, _) <- rs) { if (size("output") != l.length) return false; } @@ -66,14 +66,14 @@ test bool editsHaveLangthOfNameUnderCursor() { } test bool failsWithError() { - if (<_, _, {error(_, _), *_}> := basicRename(col = 26)) { + if (<_, {error(_, _), *_}> := basicRename(col = 26)) { return true; } return false; } test bool invalidName() { - if (<_, _, {error(_, _), *_}> := basicRename(newName = "_foo")) { + if (<_, {error(_, _), *_}> := basicRename(newName = "_foo")) { return true; } return false; diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 31054bb..3a275a8 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -42,14 +42,13 @@ import Set; import util::Reflective; -alias RenameResult = tuple[list[DocumentEdit], map[str, ChangeAnnotation], set[Message]]; +alias RenameResult = tuple[list[DocumentEdit], set[Message]]; data Renamer = renamer( void(Message) msg , void(DocumentEdit) documentEdit , void(TextEdit) textEdit - , void(str, ChangeAnnotation) annotation , RenameConfig() getConfig // Helpers @@ -145,17 +144,10 @@ RenameResult rename( } }; - map[str id, ChangeAnnotation annotation] annotations = (); - void registerAnnotation(str annotationId, ChangeAnnotation annotation) { - if (annotationId in annotations) registerMessage(error("An annotation with id \'\' already exists!")); - annotations[annotationId] = annotation; - }; - Renamer r = renamer( registerMessage , registerDocumentEdit , registerTextEdit - , registerAnnotation , RenameConfig() { return config; } , void(str s, loc at) { registerMessage(info(s, at)); } , void(str s, loc at) { registerMessage(warning(s, at)); } @@ -167,7 +159,7 @@ RenameResult rename( if (debug) println("+ Finding rename candidates for cursor at "); = config.findCandidates(cursor, parseLocCached, getTModelCached, r); if (defs == {}) r.error("No definitions found", cursor[0].src); - if (errorReported()) return ; + if (errorReported()) return ; set[loc] candidates = {l.top | l <- uses} + {d.defined.top | d <- defs}; for (loc f <- candidates) { @@ -208,7 +200,6 @@ RenameResult rename( println(" # of text edits: "); println(" # of messages: "); println(" ( errors, warnings and infos)"); - println(" # of annotations: "); if (size(messages) > 0) { println("\n===============\nMessages\n==============="); @@ -219,5 +210,5 @@ RenameResult rename( } } - return ; + return ; } diff --git a/src/main/rascal/refactor/TextEdits.rsc b/src/main/rascal/refactor/TextEdits.rsc index 7cf4144..d670869 100644 --- a/src/main/rascal/refactor/TextEdits.rsc +++ b/src/main/rascal/refactor/TextEdits.rsc @@ -31,13 +31,8 @@ extend analysis::diff::edits::TextEdits; import util::Maybe; -alias ChangeAnnotationId = str; - data ChangeAnnotation = changeAnnotation(str label, str description, bool needsConfirmation) ; data TextEdit(Maybe[ChangeAnnotation] annotation = nothing()); - -alias ChangeAnnotationRegister = - ChangeAnnotationId(str label, str description, bool needsConfirmation); From b28d9fc39f03ac08f5cde8656c6d3fd27a132006 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 21 Jan 2025 16:43:23 +0100 Subject: [PATCH 14/20] Move to typepal messages with formatting. --- src/main/rascal/refactor/Rename.rsc | 48 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 3a275a8..e9fd787 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -29,6 +29,8 @@ module refactor::Rename import refactor::TextEdits; +import analysis::typepal::FailMessage; +import analysis::typepal::Messenger; import analysis::typepal::TModel; @@ -46,7 +48,7 @@ alias RenameResult = tuple[list[DocumentEdit], set[Message]]; data Renamer = renamer( - void(Message) msg + void(FailMessage) msg , void(DocumentEdit) documentEdit , void(TextEdit) textEdit , RenameConfig() getConfig @@ -89,9 +91,18 @@ RenameResult rename( } // Messages - set[Message] messages = {}; + set[FailMessage] messages = {}; bool errorReported() = messages != {} && any(m <- messages, m is error); - void registerMessage(Message msg) { messages += msg; }; + void registerMessage(FailMessage msg) { messages += msg; }; + AType getType(Tree t) { + TModel tm = getTModelCached(parseLocCached(t.src.top)); + if (tm.facts[t.src]?) { + return tm.facts[t.src]; + } + + return tvar(|unknown:///|); + } + set[Message] getMessages() = {toMessage(m, getType) | m <- messages}; // Edits set[loc] editsSeen = {}; @@ -99,7 +110,7 @@ RenameResult rename( void checkEdit(replace(loc range, _)) { if (range in editsSeen) { - registerMessage(error("Multiple replace edits for this location.", range)); + registerMessage(error(range, "Multiple replace edits for this location.")); } editsSeen += range; } @@ -111,14 +122,14 @@ RenameResult rename( for (te:replace(range, _) <- tes) { // Check integrity if (range.top != f) { - registerMessage(error("Invalid replace edit for this location. This location is not in , for which it was registered.", range)); + registerMessage(error(range, "Invalid replace edit for this location. This location is not in , for which it was registered.")); } // Check text edits checkEdit(te); } } else if (file in editsSeen) { - registerMessage(error("Multiple edits for this file.", file)); + registerMessage(error(file, "Multiple edits for this file.")); } editsSeen += file; @@ -149,9 +160,9 @@ RenameResult rename( , registerDocumentEdit , registerTextEdit , RenameConfig() { return config; } - , void(str s, loc at) { registerMessage(info(s, at)); } - , void(str s, loc at) { registerMessage(warning(s, at)); } - , void(str s, loc at) { registerMessage(error(s, at)); } + , void(str s, value at) { registerMessage(info(at, s)); } + , void(str s, value at) { registerMessage(warning(at, s)); } + , void(str s, value at) { registerMessage(error(at, s)); } ); if (debug) println("Renaming to \'\'"); @@ -159,7 +170,7 @@ RenameResult rename( if (debug) println("+ Finding rename candidates for cursor at "); = config.findCandidates(cursor, parseLocCached, getTModelCached, r); if (defs == {}) r.error("No definitions found", cursor[0].src); - if (errorReported()) return ; + if (errorReported()) return ; set[loc] candidates = {l.top | l <- uses} + {d.defined.top | d <- defs}; for (loc f <- candidates) { @@ -186,29 +197,32 @@ RenameResult rename( } if (debug) println(" - Done!"); } + + set[Message] convertedMessages = getMessages(); + if (debug) println("+ Done!"); if (debug) { println("\n\n=================\nRename statistics\n=================\n"); int nDocs = size({f | de <- docEdits, f := (de has file ? de.file : de.from)}); int nEdits = (0 | it + ((changed(_, tes) := e) ? size(tes) : 1) | e <- docEdits); - int nErrors = size({msg | msg <- messages, msg is error}); - int nWarnings = size({msg | msg <- messages, msg is warning}); - int nInfos = size({msg | msg <- messages, msg is info}); + int nErrors = size({msg | msg <- convertedMessages, msg is error}); + int nWarnings = size({msg | msg <- convertedMessages, msg is warning}); + int nInfos = size({msg | msg <- convertedMessages, msg is info}); println(" # of documents affected: "); println(" # of text edits: "); - println(" # of messages: "); + println(" # of messages: "); println(" ( errors, warnings and infos)"); - if (size(messages) > 0) { + if (size(convertedMessages) > 0) { println("\n===============\nMessages\n==============="); - for (msg <- messages) { + for (msg <- convertedMessages) { println(" ** "); } println(); } } - return ; + return ; } From dea82cca4d97cdedde2a65284fb035ec6aabe5a9 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 21 Jan 2025 16:54:19 +0100 Subject: [PATCH 15/20] Remove obsolete tests. --- src/main/rascal/refactor/RenameTest.rsc | 62 ------------------------- 1 file changed, 62 deletions(-) delete mode 100644 src/main/rascal/refactor/RenameTest.rsc diff --git a/src/main/rascal/refactor/RenameTest.rsc b/src/main/rascal/refactor/RenameTest.rsc deleted file mode 100644 index 01fc0b9..0000000 --- a/src/main/rascal/refactor/RenameTest.rsc +++ /dev/null @@ -1,62 +0,0 @@ -@license{ -Copyright (c) 2024-2025, Swat.engineering -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -} -module refactor::RenameTest - -import refactor::Rename; - -test bool mergeNoTextEdits() = - mergeTextEdits([]) == []; - -test bool mergeTextEditsToSingleFile() = - mergeTextEdits([ - changed(|memory:///file1|, [replace(|memory:///file1|(0, 0, <0, 0>, <0, 0>), "")]) - , changed(|memory:///file1|, [replace(|memory:///file1|(1, 0, <0, 0>, <0, 0>), "")]) - , changed(|memory:///file1|, [replace(|memory:///file1|(2, 0, <0, 0>, <0, 0>), "")]) - ]) == [ - changed(|memory:///file1|, [ - replace(|memory:///file1|(0, 0, <0, 0>, <0, 0>), "") - , replace(|memory:///file1|(1, 0, <0, 0>, <0, 0>), "") - , replace(|memory:///file1|(2, 0, <0, 0>, <0, 0>), "") - ]) - ]; - -test bool mergeTextEditsWithRenameInBetween() = - mergeTextEdits([ - changed(|memory:///file1|, [replace(|memory:///file1|(0, 0, <0, 0>, <0, 0>), "")]) - , changed(|memory:///file1|, [replace(|memory:///file1|(1, 0, <0, 0>, <0, 0>), "")]) - , renamed(|memory:///file1|, |memory:///file2|) - , changed(|memory:///file1|, [replace(|memory:///file1|(2, 0, <0, 0>, <0, 0>), "")]) - ]) == [ - changed(|memory:///file1|, [ - replace(|memory:///file1|(0, 0, <0, 0>, <0, 0>), "") - , replace(|memory:///file1|(1, 0, <0, 0>, <0, 0>), "") - ]) - , renamed(|memory:///file1|, |memory:///file2|) - , changed(|memory:///file1|, [ - replace(|memory:///file1|(2, 0, <0, 0>, <0, 0>), "") - ]) - ]; From 595c821c12a86521a67fe4bcc8463487d359f3f6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 24 Jan 2025 16:06:53 +0100 Subject: [PATCH 16/20] Rewrite framework based on latest design insights - Change staging to reduce amount of iterations over project files - Provide sensible, generic default/fallback stage implementations - Use extension mechanism to override defaults when needed --- src/main/rascal/examples/modfun/Rename.rsc | 148 +++++++++++------- .../rascal/examples/modfun/RenameTest.rsc | 15 +- src/main/rascal/examples/modules/Rename.rsc | 131 ++++++++-------- .../rascal/examples/modules/RenameTest.rsc | 2 +- src/main/rascal/examples/pico/Rename.rsc | 30 ---- src/main/rascal/examples/pico/RenameTest.rsc | 15 +- src/main/rascal/refactor/Rename.rsc | 116 ++++++++++---- src/main/rascal/refactor/TextEdits.rsc | 1 - 8 files changed, 258 insertions(+), 200 deletions(-) diff --git a/src/main/rascal/examples/modfun/Rename.rsc b/src/main/rascal/examples/modfun/Rename.rsc index 8829d80..db53a6d 100644 --- a/src/main/rascal/examples/modfun/Rename.rsc +++ b/src/main/rascal/examples/modfun/Rename.rsc @@ -30,7 +30,7 @@ module examples::modfun::Rename import examples::modfun::Checker; import examples::modfun::Syntax; -import refactor::Rename; +extend refactor::Rename; import refactor::TextEdits; import Exception; @@ -42,10 +42,10 @@ import util::FileSystem; import util::Maybe; data RenameConfig( - set[loc] workspaceFolders = {} + set[loc] srcs = {} ); -public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { +public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tree] cursor, str newName) { bool nameIsValid = any(ModId _ <- cursor) ? isValidName(moduleId(), newName) : isValidName(variableId(), newName); @@ -60,62 +60,98 @@ public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tre , rconfig( Tree(loc l) { return parse(#start[ModFun], l); } , TModel(Tree pt) { return collectAndSolve(pt, config = tconfig(mayOverload = bool(set[loc] defs, map[loc, Define] defines) { return true; })); } - , findCandidates - , renameDef - , renameUses - , skipCandidate = skipCandidate - , workspaceFolders = workspaceFolders + , srcs = {cursor[0].src.top.parent} ) ); } -tuple[set[Define], set[loc]] findCandidates(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { - set[Define] defs = {}; - set[loc] uses = {}; - str cursorName = ""; +private Maybe[tuple[IdRole, str]] analyzeUse((ImportDecl) `import ;`) = just(">); +private Maybe[tuple[IdRole, str]] analyzeUse((Expression) ``) = just(">); +private default Maybe[tuple[IdRole, str]] analyzeUse(Tree _) = nothing(); + +private Maybe[tuple[IdRole, str]] analyzeDef((ModuleDecl) `module { }`) = just(">); +private Maybe[tuple[IdRole, str]] analyzeDef((VarDecl) `def : = ;`) = just(">); +private default Maybe[tuple[IdRole, str]] analyzeDef(Tree _) = nothing(); + +set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getModel, Renamer r) { + TModel tm = getModel(cursor[-1]); + for (Tree c <- cursor) { + if (tm.definitions[c.src]?) { + return {tm.definitions[c.src]}; + } else { + str cursorName = ""; + set[Define] defs = {}; + for (referToDef(use(modId, _, _, _, {moduleId(), *_}), importPath()) <- tm.referPaths + , loc f := cursor[0].src.top.parent + ".mfun") { + Tree fTree = getTree(f); + for (/Tree t := fTree) { + if (just() := analyzeDef(t)) { + tm = getModel(fTree); + defs += {d | Define d:<_, cursorName, _, idRole, _, _> <- tm.defines}; + } + } + } + if (defs != {}) return defs; + } + } + + fail; +} - for (loc wsFolder <- r.getConfig().workspaceFolders - , loc f <- find(wsFolder, "mfun")) { - Tree t = getTree(f); +tuple[set[loc], set[loc]] findOccurrenceFiles(set[Define] defs, list[Tree] cursor, Tree(loc) getTree, Renamer r) { + set[loc] defFiles = {}; + set[loc] useFiles = {}; - Maybe[TModel] tmodelCache = nothing(); - TModel getLocalTModel() { - if (tmodelCache is nothing) { - tmodelCache = just(getTModel(t)); + for (Define _:<_, name, _, idRole, _, _> <- defs) { + for (loc srcFolder <- r.getConfig().srcs + , loc f <- find(srcFolder, "mfun")) { + for (/Tree t := getTree(f)) { + if (just() := analyzeDef(t)) defFiles += f; + if (just() := analyzeUse(t)) useFiles += f; } - return tmodelCache.val; } + } + + return ; +} + +tuple[set[loc] defFiles, set[loc] useFiles] findOccurrenceFiles(set[Define] defs, list[Tree] cursor, Tree(loc) getTree, Renamer r) { + set[loc] defFiles = {}; + set[loc] useFiles = {}; - visit(t) { - case (ModuleDecl) `module { }`: { - if ("" == cursorName) { - defs += getLocalTModel().definitions[id.src]; + for (Define _: <_, name, _, idRole, _, _> <- defs) { + for (loc srcFolder <- r.getConfig().srcs + , loc f <- find(srcFolder, "mfun")) { + Tree t = getTree(f); + + visit(t) { + case (ModuleDecl) `module { }`: { + if ("" == name) { + defFiles += f; + } } - } - case (ImportDecl) `import ;`: { - if ("" == cursorName) { - uses += id.src; + case (ImportDecl) `import ;`: { + if ("" == name) { + useFiles += f; + } } - } - case (VarDecl) `def : = ;`: { - if ("" == cursorName) { - defs += getLocalTModel().definitions[id.src]; + case (VarDecl) `def : = ;`: { + if ("" == name) { + defFiles += f; + } } - } - case (Expression) ``: { - if ("" == cursorName) { - uses += id.src; + case (Expression) ``: { + if ("" == name) { + useFiles += f; + } } } } } - return ; + return ; } -set[loc] findCandidateFiles(set[loc] workspaceFolders) = - {*ls | loc wsFolder <- workspaceFolders, ls := find(wsFolder, "modules")}; - bool tryParse(type[&T <: Tree] tp, str s) { try { parse(tp, s); @@ -128,26 +164,24 @@ bool tryParse(type[&T <: Tree] tp, str s) { bool isValidName(moduleId(), str name) = tryParse(#ModId, name); bool isValidName(variableId(), str name) = tryParse(#Id, name); -bool skipCandidate(set[Define] defs, Tree modTree, Renamer _) { - // Only if the name of the definition appears in the module, consider it a rename candidate - set[str] names = {d.id | d <- defs}; - return !(any(/Id t := modTree, "" in names) - || any(/ModId t := modTree, "" in names)); -} - -void renameDef(Define def, str newName, TModel tm, Renamer r) { - if (def.defined in tm.defines.defined) { - r.textEdit(replace(def.defined, newName)); +set[Define] findAdditionalDefinitions(set[Define] cursorDefs, Tree tr, TModel tm) { + set[Define] overloads = {}; + for (d <- tm.defines + && d.idRole in cursorDefs.idRole + && d.id in cursorDefs.id + && d.defined notin cursorDefs.defined) { + if (tm.config.mayOverload(cursorDefs.defined + d.defined, tm.definitions)) { + overloads += d; + } } + return overloads; } -void renameUses(set[Define] defs, str newName, set[loc] useCandidates, TModel tm, Renamer r) { - set[loc] uses = {occ - | , - use(id, orgId, loc occ, _, {idRole, *_})> - <- defs * toSet(tm.uses) - }; - for (loc u <- useCandidates & uses) { +void renameUses(set[Define] defs, str newName, TModel tm, Renamer r) { + // Somehow, tm.useDef is empty, so we need to use tm.uses + rel[loc, loc] defUse = { | , use(id, orgId, u, _, _)> <- defs * toSet(tm.uses)}; + + for (loc u <- defUse[defs.defined]) { r.textEdit(replace(u, newName)); } } diff --git a/src/main/rascal/examples/modfun/RenameTest.rsc b/src/main/rascal/examples/modfun/RenameTest.rsc index 2299618..5401997 100644 --- a/src/main/rascal/examples/modfun/RenameTest.rsc +++ b/src/main/rascal/examples/modfun/RenameTest.rsc @@ -43,9 +43,12 @@ import util::FileSystem; tuple[list[DocumentEdit] edits, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { prog = parse(#start[ModFun], |project://rename-framework/src/main/rascal/examples/modfun/.mfun|); cursor = computeFocusList(prog, line, col); - return renameModules(cursor, newName, {|project://rename-framework/src/main/rascal/examples/modfun/|}); + return renameModules(cursor, newName); } +list[int] sortedEditAmounts(list[DocumentEdit] edits) = + sort([size(e.edits) | e <- edits]); + void checkNoErrors(set[Message] msgs) { if (m <- msgs, m is error) { throw "Renaming threw errors:\n - "; @@ -56,10 +59,8 @@ test bool moduleName() { = basicRename("A", 1, 8, newName = "B"); checkNoErrors(msgs); - iprintln(edits); return size(edits) == 2 - && size(edits[0].edits) == 1 - && size(edits[1].edits) == 1; + && sortedEditAmounts(edits) == [1, 1]; } test bool overloadedFunc() { @@ -67,8 +68,7 @@ test bool overloadedFunc() { checkNoErrors(msgs); return size(edits) == 2 - && size(edits[0].edits) == 1 - && size(edits[1].edits) == 2; + && sortedEditAmounts(edits) == [1, 2]; } test bool importedFunc() { @@ -76,6 +76,5 @@ test bool importedFunc() { checkNoErrors(msgs); return size(edits) == 2 - && size(edits[0].edits) == 1 - && size(edits[1].edits) == 2; + && sortedEditAmounts(edits) == [1, 2]; } diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index 1d6796a..a144c1e 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -30,7 +30,7 @@ module examples::modules::Rename import examples::modules::Checker; import examples::modules::Syntax; -import refactor::Rename; +extend refactor::Rename; import refactor::TextEdits; import Exception; @@ -40,12 +40,13 @@ import Relation; import Set; import util::FileSystem; import util::Maybe; +import util::Reflective; data RenameConfig( - set[loc] workspaceFolders = {} + PathConfig pcfg = pathConfig() ); -public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tree] cursor, str newName, set[loc] workspaceFolders) { +public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tree] cursor, str newName) { bool nameIsValid = any(ModuleId _ <- cursor) ? isValidName(moduleId(), newName) : isValidName(structId(), newName); @@ -60,61 +61,75 @@ public tuple[list[DocumentEdit] edits, set[Message] msgs] renameModules(list[Tre , rconfig( Tree(loc l) { return parse(#start[Program], l); } , collectAndSolve - , findCandidates - , renameDef - , renameUses - , skipCandidate = skipCandidate - , workspaceFolders = workspaceFolders + , pcfg = pathConfig(srcs = [cursor[0].src.top.parent]) ) ); } -tuple[set[Define], set[loc]] findCandidates(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) { - str cursorName = ""; - - set[Define] defs = {}; - set[loc] uses = {}; - - for (loc wsFolder <- r.getConfig().workspaceFolders - , loc f <- find(wsFolder, "modules")) { - Tree t = getTree(f); +bool isValidName(moduleId(), str name) = tryParse(#ModuleId, name); +bool isValidName(structId(), str name) = tryParse(#Id, name); - Maybe[TModel] tmCache = nothing(); - TModel getLocalTModel() { - if (tmCache is nothing) { - tmCache = just(getTModel(t)); +set[loc] projectFiles(pathConfig(srcs = srcs)) = + {*fs | dir <- srcs + , fs := find(dir, "modules")}; + +private Maybe[tuple[IdRole, str]] analyzeUse((Import) `import `) = just(">); +private Maybe[tuple[IdRole, str]] analyzeUse((DeclInStruct) ``) = just(">); +private default Maybe[tuple[IdRole, str]] analyzeUse(Tree _) = nothing(); + +private Maybe[tuple[IdRole, str]] analyzeDef((Program) `module `) = just(">); +private Maybe[tuple[IdRole, str]] analyzeDef((TopLevelDecl) `struct { }`) = just(">); +private default Maybe[tuple[IdRole, str]] analyzeDef(Tree _) = nothing(); + +set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getModel, Renamer r) { + TModel tm = getModel(cursor[-1]); + for (Tree c <- cursor) { + if (tm.definitions[c.src]?) { + return {tm.definitions[c.src]}; + } else { + str cursorName = ""; + pcfg = r.getConfig().pcfg; + set[Define] defs = {}; + for (referToDef(use(modId, _, _, _, {moduleId(), *_}), importPath()) <- tm.referPaths + , := lookupModule(modId, pcfg)) { + Tree fTree = getTree(f); + for (/Tree t := fTree) { + if (just() := analyzeDef(t)) { + tm = getModel(fTree); + defs += {d | Define d:<_, cursorName, _, idRole, _, _> <- tm.defines}; + } + } } - return tmCache.val; + if (defs != {}) return defs; } + } - visit (t) { - case (Import) `import `: { - if ("" == cursorName) { - uses += id.src; - } - } - case (DeclInStruct) ``: { - if ("" == cursorName) { - uses += ty.src; - } - } - case s:(TopLevelDecl) `struct { }`: { - if ("" == cursorName - && tm := getLocalTModel() - && Define d:<_, _, _, structId(), _, _> := tm.definitions[s.src]) { - defs += d; - } - } - case m:(Program) `module `: { - if ("" == cursorName - && tm := getLocalTModel() - && Define d:<_, _, _, moduleId(), _, _> := tm.definitions[m.src]) { - defs += d; - } + fail; +} + +tuple[set[loc], set[loc]] findOccurrenceFiles(set[Define] defs, list[Tree] cursor, Tree(loc) getTree, Renamer r) { + set[loc] defFiles = {}; + set[loc] useFiles = {}; + + for (Define _:<_, name, _, idRole, _, _> <- defs) { + for (loc f <- projectFiles(r.getConfig().pcfg)) { + for (/Tree t := getTree(f)) { + if (just() := analyzeDef(t)) defFiles += f; + if (just() := analyzeUse(t)) useFiles += f; } } } - return ; + + return ; +} + +void renameUses(set[Define] defs, str newName, TModel tm, Renamer r) { + // Somehow, tm.useDef is empty, so we need to use tm.uses + rel[loc, loc] defUse = { | , use(id, orgId, u, _, _)> <- defs * toSet(tm.uses)}; + + for (loc u <- defUse[defs.defined]) { + r.textEdit(replace(u, newName)); + } } bool tryParse(type[&T <: Tree] tp, str s) { @@ -125,25 +140,3 @@ bool tryParse(type[&T <: Tree] tp, str s) { return false; } } - -bool isValidName(moduleId(), str name) = tryParse(#ModuleId, name); -bool isValidName(structId(), str name) = tryParse(#Id, name); - -bool skipCandidate(set[Define] defs, Tree modTree, Renamer _) { - // Only if the name of the definition appears in the module, consider it a rename candidate - set[str] names = {d.id | d <- defs}; - return !(any(/Id t := modTree, "" in names) - || any(/ModuleId t := modTree, "" in names)); -} - -void renameDef(Define def, str newName, TModel tm, Renamer r) { - if (def in tm.defines) { - r.textEdit(replace(def.defined, newName)); - } -} - -void renameUses(set[Define] _, str newName, set[loc] useCandidates, TModel _, Renamer r) { - for (loc u <- useCandidates) { - r.textEdit(replace(u, newName)); - } -} diff --git a/src/main/rascal/examples/modules/RenameTest.rsc b/src/main/rascal/examples/modules/RenameTest.rsc index 231c462..75c4aaa 100644 --- a/src/main/rascal/examples/modules/RenameTest.rsc +++ b/src/main/rascal/examples/modules/RenameTest.rsc @@ -43,7 +43,7 @@ import util::FileSystem; tuple[list[DocumentEdit] edits, set[Message] msgs] basicRename(str modName, int line, int col, str newName = "foo") { prog = parse(#start[Program], |lib://typepal/src/examples/modules/.modules|); cursor = computeFocusList(prog, line, col); - return renameModules(cursor, newName, {|lib://typepal/src/examples/modules|}); + return renameModules(cursor, newName); } void checkNoErrors(set[Message] msgs) { diff --git a/src/main/rascal/examples/pico/Rename.rsc b/src/main/rascal/examples/pico/Rename.rsc index dd86283..7946903 100644 --- a/src/main/rascal/examples/pico/Rename.rsc +++ b/src/main/rascal/examples/pico/Rename.rsc @@ -39,8 +39,6 @@ import IO; import Relation; import util::FileSystem; -data Tree; - public tuple[list[DocumentEdit] edits, set[Message] msgs] renamePico(list[Tree] cursor, str newName) { if (!isValidName(newName)) { return <[], {error("\'\' is not a valid name here.", cursor[0].src)}>; @@ -52,38 +50,10 @@ public tuple[list[DocumentEdit] edits, set[Message] msgs] renamePico(list[Tree] , rconfig( Tree(loc l) { return parse(#start[Program], l); } , collectAndSolve - , findCandidates - , renameDef - , renameUses - , skipCandidate = bool(_, _, _) { return false; } ) ); } -tuple[set[Define], set[loc]] findCandidates(list[Tree] cursor, Tree(loc) _, TModel(Tree) getTModel, Renamer _) { - TModel tm = getTModel(cursor[-1]); - if (Tree t <- cursor - , tm.definitions[t.src]?) { - set[Define] defs = {tm.definitions[t.src]}; - set[loc] uses = invert(tm.useDef)[defs.defined]; - return ; - } - - return <{}, {}>; -} - -void renameDef(Define def, str newName, TModel tm, Renamer r) { - // Register edit for definitions in this file - r.textEdit(replace(def.defined, newName)); -} - -void renameUses(set[Define] _, str newName, set[loc] candidates, TModel tm, Renamer r) { - // Register edit for uses of def in this file - for (loc u <- candidates) { - r.textEdit(replace(u, newName)); - } -} - bool isValidName(str name) { try { parse(#Id, name); diff --git a/src/main/rascal/examples/pico/RenameTest.rsc b/src/main/rascal/examples/pico/RenameTest.rsc index fd82ce6..28a05ba 100644 --- a/src/main/rascal/examples/pico/RenameTest.rsc +++ b/src/main/rascal/examples/pico/RenameTest.rsc @@ -47,18 +47,27 @@ tuple[list[DocumentEdit] edits, set[Message] msgs] basicRename(str newName = "fo return renamePico(cursor, newName); } +void checkNoErrors(set[Message] msgs) { + if (m <- msgs, m is error) { + throw "Renaming threw errors:\n - "; + } +} + test bool doesNotCrash() { - = basicRename(); + = basicRename(); + checkNoErrors(msgs); return true; } test bool hasFiveChanges() { - = basicRename(); + = basicRename(); + checkNoErrors(msgs); return size(edits) == 1 && size(edits[0].edits) == 5; } test bool editsHaveLangthOfNameUnderCursor() { - = basicRename(); + = basicRename(); + checkNoErrors(msgs); for (changed(_, rs) <- edits, replace(loc l, _) <- rs) { if (size("output") != l.length) return false; } diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index e9fd787..3c3e375 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -24,7 +24,6 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } -@bootstrapParser module refactor::Rename import refactor::TextEdits; @@ -40,10 +39,9 @@ import Map; import Message; import Node; import ParseTree; +import Relation; import Set; -import util::Reflective; - alias RenameResult = tuple[list[DocumentEdit], set[Message]]; data Renamer @@ -63,10 +61,6 @@ data RenameConfig = rconfig( Tree(loc) parseLoc , TModel(Tree) tmodelForTree - , tuple[set[Define] defs, set[loc] uses](list[Tree] cursor, Tree(loc) getTree, TModel(Tree) getTModel, Renamer r) findCandidates - , void(Define def, str newName, TModel tm, Renamer r) renameDef - , void(set[Define] defs, str newName, set[loc] candidates, TModel tm, Renamer r) renameUses - , bool(set[Define] defs, Tree t, Renamer r) skipCandidate = bool(_, _, _) { return false; } ); RenameResult rename( @@ -90,6 +84,9 @@ RenameResult rename( return config.parseLoc(l); } + // Make sure user uses cached functions + cachedConfig = config[parseLoc = parseLocCached][tmodelForTree = getTModelCached]; + // Messages set[FailMessage] messages = {}; bool errorReported() = messages != {} && any(m <- messages, m is error); @@ -159,7 +156,7 @@ RenameResult rename( registerMessage , registerDocumentEdit , registerTextEdit - , RenameConfig() { return config; } + , RenameConfig() { return cachedConfig; } , void(str s, value at) { registerMessage(info(at, s)); } , void(str s, value at) { registerMessage(warning(at, s)); } , void(str s, value at) { registerMessage(error(at, s)); } @@ -167,35 +164,50 @@ RenameResult rename( if (debug) println("Renaming to \'\'"); - if (debug) println("+ Finding rename candidates for cursor at "); - = config.findCandidates(cursor, parseLocCached, getTModelCached, r); + if (debug) println("+ Finding definitions for cursor at "); + defs = getCursorDefinitions(cursor, parseLocCached, getTModelCached, r); + if (defs == {}) r.error("No definitions found", cursor[0].src); if (errorReported()) return ; - set[loc] candidates = {l.top | l <- uses} + {d.defined.top | d <- defs}; - for (loc f <- candidates) { - if (debug) println(" - Processing candidate "); - set[loc] fileUses = {u | u <- uses, u.top == f}; - - if (debug) println(" + Retrieving parse tree"); - Tree t = parseLocCached(f); - if (config.skipCandidate(defs, t, r)) { - if (debug) println(" + Skipping"); - continue; + if (debug) println("+ Finding occurrences of cursor"); + = findOccurrenceFiles(defs, cursor, parseLocCached, r); + + if (maybeDefFiles != {}) { + if (debug) println("+ Finding additional definitions"); + set[Define] additionalDefs = {}; + for (loc f <- maybeDefFiles) { + if (debug) println(" - ... in "); + tr = parseLocCached(f); + tm = getTModelCached(tr); + additionalDefs += findAdditionalDefinitions(defs, tr, tm); } + defs += additionalDefs; + } - if (debug) println(" + Retrieving TModel"); - TModel tm = getTModelCached(t); - if (debug) println(" + Renaming each definition"); - for (Define d <- defs) { - if (debug) println(" - Renaming \'\' @ "); - config.renameDef(d, newName, tm, r); - } - if (fileUses != {}) { - if (debug) println(" - Renaming uses @ "); - config.renameUses(defs, newName, fileUses, tm, r); + defFiles = {d.defined.top | d <- defs}; + + if (debug) println("+ Renaming definitions across files"); + for (loc f <- defFiles) { + fileDefs = {d | d <- defs, d.defined.top == f}; + if (debug) println(" - ... in "); + + tr = parseLocCached(f); + tm = getTModelCached(tr); + + for (d <- defs, d.defined.top == f) { + renameDefinition(d, newName, tm, r); } - if (debug) println(" - Done!"); + } + + if (debug) println("+ Renaming uses across files"); + for (loc f <- maybeUseFiles) { + if (debug) println(" - ... in "); + + tr = parseLocCached(f); + tm = getTModelCached(tr); + + renameUses(defs, newName, tm, r); } set[Message] convertedMessages = getMessages(); @@ -226,3 +238,45 @@ RenameResult rename( return ; } + +default set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) _, TModel(Tree) getModel, Renamer r) { + loc cursorLoc = cursor[0].src; + TModel tm = getModel(cursor[-1]); + for (Tree c <- cursor) { + if (tm.definitions[c.src]?) { + return {tm.definitions[c.src]}; + } else if (defs: {_, *_} := tm.useDef[c.src]) { + if (any(d <- defs, d.top != cursorLoc.top)) { + r.error("Rename not implemented for cross-file definitions. Please overload `getCursorDefinitions`.", cursorLoc); + return {}; + } + + return {tm.definitions[d] | d <- defs, tm.definitions[d]?}; + } + } + + r.error("Could not find definition to rename.", cursorLoc); + return {}; +} + +default tuple[set[loc] defFiles, set[loc] useFiles] findOccurrenceFiles(set[Define] cursorDefs, list[Tree] cursor, Tree(loc) _, Renamer r) { + loc f = cursor[0].src.top; + if (any(d <- cursorDefs, f != d.defined.top)) { + r.error("Rename not implemented for cross-file definitions. Please overload `findOccurrenceFiles`.", cursor[0].src); + return <{}, {}>; + } + + return <{f}, {f}>; +} + +default set[Define] findAdditionalDefinitions(set[Define] cursorDefs, Tree tr, TModel tm) = {}; + +default void renameDefinition(Define d, str newName, TModel tm, Renamer r) { + r.textEdit(replace(d.defined, newName)); +} + +default void renameUses(set[Define] defs, str newName, TModel tm, Renamer r) { + for (loc u <- invert(tm.useDef)[defs.defined]) { + r.textEdit(replace(u, newName)); + } +} \ No newline at end of file diff --git a/src/main/rascal/refactor/TextEdits.rsc b/src/main/rascal/refactor/TextEdits.rsc index d670869..e41e609 100644 --- a/src/main/rascal/refactor/TextEdits.rsc +++ b/src/main/rascal/refactor/TextEdits.rsc @@ -24,7 +24,6 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } -@bootstrapParser module refactor::TextEdits extend analysis::diff::edits::TextEdits; From 41a37f9b646c4e923122291e1a0449506d255489 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 24 Jan 2025 16:10:37 +0100 Subject: [PATCH 17/20] Move debug flag to config. --- src/main/rascal/refactor/Rename.rsc | 32 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 3c3e375..704719a 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -61,13 +61,19 @@ data RenameConfig = rconfig( Tree(loc) parseLoc , TModel(Tree) tmodelForTree + , bool debug = true ); RenameResult rename( list[Tree] cursor , str newName - , RenameConfig config - , bool debug = true) { + , RenameConfig config) { + + void printDebug(str s) { + if (config.debug) { + println(s); + } + } // Tree & TModel caching @@ -162,22 +168,22 @@ RenameResult rename( , void(str s, value at) { registerMessage(error(at, s)); } ); - if (debug) println("Renaming to \'\'"); + printDebug("Renaming to \'\'"); - if (debug) println("+ Finding definitions for cursor at "); + printDebug("+ Finding definitions for cursor at "); defs = getCursorDefinitions(cursor, parseLocCached, getTModelCached, r); if (defs == {}) r.error("No definitions found", cursor[0].src); if (errorReported()) return ; - if (debug) println("+ Finding occurrences of cursor"); + printDebug("+ Finding occurrences of cursor"); = findOccurrenceFiles(defs, cursor, parseLocCached, r); if (maybeDefFiles != {}) { - if (debug) println("+ Finding additional definitions"); + printDebug("+ Finding additional definitions"); set[Define] additionalDefs = {}; for (loc f <- maybeDefFiles) { - if (debug) println(" - ... in "); + printDebug(" - ... in "); tr = parseLocCached(f); tm = getTModelCached(tr); additionalDefs += findAdditionalDefinitions(defs, tr, tm); @@ -187,10 +193,10 @@ RenameResult rename( defFiles = {d.defined.top | d <- defs}; - if (debug) println("+ Renaming definitions across files"); + printDebug("+ Renaming definitions across files"); for (loc f <- defFiles) { fileDefs = {d | d <- defs, d.defined.top == f}; - if (debug) println(" - ... in "); + printDebug(" - ... in "); tr = parseLocCached(f); tm = getTModelCached(tr); @@ -200,9 +206,9 @@ RenameResult rename( } } - if (debug) println("+ Renaming uses across files"); + printDebug("+ Renaming uses across files"); for (loc f <- maybeUseFiles) { - if (debug) println(" - ... in "); + printDebug(" - ... in "); tr = parseLocCached(f); tm = getTModelCached(tr); @@ -212,8 +218,8 @@ RenameResult rename( set[Message] convertedMessages = getMessages(); - if (debug) println("+ Done!"); - if (debug) { + printDebug("+ Done!"); + if (config.debug) { println("\n\n=================\nRename statistics\n=================\n"); int nDocs = size({f | de <- docEdits, f := (de has file ? de.file : de.from)}); int nEdits = (0 | it + ((changed(_, tes) := e) ? size(tes) : 1) | e <- docEdits); From 659ea81bc44520734eec20fbb351b34eed57d3cf Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 24 Jan 2025 16:25:24 +0100 Subject: [PATCH 18/20] Align message helpers with FailMessage. --- src/main/rascal/refactor/Rename.rsc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 704719a..158675b 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -52,9 +52,9 @@ data Renamer , RenameConfig() getConfig // Helpers - , void(str, loc) warning - , void(str, loc) info - , void(str, loc) error + , void(value, str) warning + , void(value, str) info + , void(value, str) error ); data RenameConfig @@ -163,9 +163,9 @@ RenameResult rename( , registerDocumentEdit , registerTextEdit , RenameConfig() { return cachedConfig; } - , void(str s, value at) { registerMessage(info(at, s)); } - , void(str s, value at) { registerMessage(warning(at, s)); } - , void(str s, value at) { registerMessage(error(at, s)); } + , void(value at, str s) { registerMessage(info(at, s)); } + , void(value at, str s) { registerMessage(warning(at, s)); } + , void(value at, str s) { registerMessage(error(at, s)); } ); printDebug("Renaming to \'\'"); @@ -173,7 +173,7 @@ RenameResult rename( printDebug("+ Finding definitions for cursor at "); defs = getCursorDefinitions(cursor, parseLocCached, getTModelCached, r); - if (defs == {}) r.error("No definitions found", cursor[0].src); + if (defs == {}) r.error(cursor[0].src, "No definitions found"); if (errorReported()) return ; printDebug("+ Finding occurrences of cursor"); @@ -253,7 +253,7 @@ default set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) _, TModel( return {tm.definitions[c.src]}; } else if (defs: {_, *_} := tm.useDef[c.src]) { if (any(d <- defs, d.top != cursorLoc.top)) { - r.error("Rename not implemented for cross-file definitions. Please overload `getCursorDefinitions`.", cursorLoc); + r.error(cursorLoc, "Rename not implemented for cross-file definitions. Please overload `getCursorDefinitions`."); return {}; } @@ -261,14 +261,14 @@ default set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) _, TModel( } } - r.error("Could not find definition to rename.", cursorLoc); + r.error(cursorLoc, "Could not find definition to rename."); return {}; } default tuple[set[loc] defFiles, set[loc] useFiles] findOccurrenceFiles(set[Define] cursorDefs, list[Tree] cursor, Tree(loc) _, Renamer r) { loc f = cursor[0].src.top; if (any(d <- cursorDefs, f != d.defined.top)) { - r.error("Rename not implemented for cross-file definitions. Please overload `findOccurrenceFiles`.", cursor[0].src); + r.error(cursor[0].src, "Rename not implemented for cross-file definitions. Please overload `findOccurrenceFiles`."); return <{}, {}>; } From 19053c8b770dfcde5711f85aee63697b077a9246 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 24 Jan 2025 16:31:06 +0100 Subject: [PATCH 19/20] Preserve change annotations on text edits. --- src/main/rascal/refactor/Rename.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/rascal/refactor/Rename.rsc b/src/main/rascal/refactor/Rename.rsc index 158675b..43350d9 100644 --- a/src/main/rascal/refactor/Rename.rsc +++ b/src/main/rascal/refactor/Rename.rsc @@ -147,11 +147,11 @@ RenameResult rename( checkEdit(e); loc f = e.range.top; - if ([*pre, changed(f, prev)] := docEdits) { + if ([*pre, c:changed(f, _)] := docEdits) { // If possible, merge with latest document edit // TODO Just assign to docEdits[-1], once this issue has been solved: // https://github.com/usethesource/rascal/issues/2123 - docEdits = [*pre, changed(f, prev + e)]; + docEdits = [*pre, c[edits = c.edits + e]]; } else { // Else, create new document edit docEdits += changed(f, [e]); From 7767f1a60ced0d1f087c373f4dd6f5f82033b01c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 29 Jan 2025 14:20:12 +0100 Subject: [PATCH 20/20] Do not rely on default implementation to throw error. --- src/main/rascal/examples/modfun/Rename.rsc | 3 ++- src/main/rascal/examples/modules/Rename.rsc | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/rascal/examples/modfun/Rename.rsc b/src/main/rascal/examples/modfun/Rename.rsc index db53a6d..d4470db 100644 --- a/src/main/rascal/examples/modfun/Rename.rsc +++ b/src/main/rascal/examples/modfun/Rename.rsc @@ -95,7 +95,8 @@ set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tr } } - fail; + r.error(cursor[0].src, "Could not find definition to rename."); + return {}; } tuple[set[loc], set[loc]] findOccurrenceFiles(set[Define] defs, list[Tree] cursor, Tree(loc) getTree, Renamer r) { diff --git a/src/main/rascal/examples/modules/Rename.rsc b/src/main/rascal/examples/modules/Rename.rsc index a144c1e..7c8be70 100644 --- a/src/main/rascal/examples/modules/Rename.rsc +++ b/src/main/rascal/examples/modules/Rename.rsc @@ -104,7 +104,8 @@ set[Define] getCursorDefinitions(list[Tree] cursor, Tree(loc) getTree, TModel(Tr } } - fail; + r.error(cursor[0].src, "Could not find definition to rename."); + return {}; } tuple[set[loc], set[loc]] findOccurrenceFiles(set[Define] defs, list[Tree] cursor, Tree(loc) getTree, Renamer r) {