From 6039db7792dcc7f216cb452a02c96ba7f162d1d6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 21 Jun 2024 14:37:45 +0200 Subject: [PATCH 001/105] Refactor to workspace-wide renaming. --- .../main/rascal/lang/rascal/lsp/Rename.rsc | 109 +++++++++--------- .../rascal/lang/rascal/lsp/WorkspaceInfo.rsc | 53 +++++++++ .../main/rascal/lang/rascal/tests/Rename.rsc | 13 ++- 3 files changed, 116 insertions(+), 59 deletions(-) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc index fd402225a..387ff7a84 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc @@ -47,11 +47,12 @@ import String; import lang::rascal::\syntax::Rascal; -import lang::rascalcore::check::Checker; import lang::rascalcore::check::Import; +import lang::rascalcore::check::RascalConfig; import analysis::typepal::TypePal; -import analysis::typepal::TModel; + +import lang::rascal::lsp::WorkspaceInfo; import analysis::diff::edits::TextEdits; @@ -66,6 +67,7 @@ data IllegalRenameReason = invalidName(str name) | doubleDeclaration(loc old, set[loc] new) | captureChange(set[Capture] captures) + | definitionsOutsideWorkspace(set[loc] defs) ; data RenameException @@ -74,14 +76,6 @@ data RenameException | unexpectedFailure(str message) ; -private set[loc] getUses(TModel tm, loc def) { - return invert(getUseDef(tm))[def]; -} - -private set[Define] getDefines(TModel tm, loc use) { - return {tm.definitions[d] | d <- tm.useDef[use]}; -} - void throwIfNotEmpty(&T(&A) R, &A arg) { if (arg != {}) { throw R(arg); @@ -97,25 +91,28 @@ set[IllegalRenameReason] checkLegalName(str name) { } } -set[IllegalRenameReason] checkCausesDoubleDeclarations(TModel tm, set[Define] currentDefs, set[Define] newDefs) { +set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = { definitionsOutsideWorkspace(d) | d <- groupRangeByDomain({ | d <- defs, f := d.top, f notin ws.modules}) }; + +set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Define] currentDefs, set[Define] newDefs) { // Is newName already resolvable from a scope where is currently declared? + map[loc, Define] defMap = (def.defined: def | def <- ws.defines); rel[loc old, loc new] doubleDeclarations = { | Define cD <- currentDefs , Define nD <- newDefs , isContainedIn(cD.defined, nD.scope) - , !rascalMayOverload({cD.defined, nD.defined}, tm.definitions) + , !rascalMayOverload({cD.defined, nD.defined}, defMap) }; return {doubleDeclaration(old, doubleDeclarations[old]) | old <- doubleDeclarations.old}; } -set[Define] findImplicitDefinitions(TModel tm, start[Module] m, set[Define] newDefs) { +set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { set[loc] maybeImplicitDefs = {l | /QualifiedName n := m, just(l) := locationOfName(n)}; - return {def | Define def <- newDefs, (def.idRole is variableId && def.defined in tm.useDef<0>) + return {def | Define def <- newDefs, (def.idRole is variableId && def.defined in ws.useDef<0>) || (def.idRole is patternVariableId && def.defined in maybeImplicitDefs)}; } -set[IllegalRenameReason] checkCausesCaptures(TModel tm, start[Module] m, set[Define] currentDefs, set[loc] currentUses, set[Define] newDefs) { - set[Define] newNameImplicitDefs = findImplicitDefinitions(tm, m, newDefs); +set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, set[Define] newDefs) { + set[Define] newNameImplicitDefs = findImplicitDefinitions(ws, m, newDefs); set[Define] newNameExplicitDefs = newDefs - newNameImplicitDefs; // Will this rename turn an implicit declaration of `newName` into a use of a current declaration? @@ -128,7 +125,7 @@ set[IllegalRenameReason] checkCausesCaptures(TModel tm, start[Module] m, set[Def // Will this rename hide a used definition of `oldName` behind an existing definition of `newName` (shadowing)? set[Capture] currentUseShadowedByRename = { | Define nD <- newDefs - , <- ident(currentUses) o tm.useDef o tm.defines + , <- ident(currentUses) o ws.useDef o ws.defines , isContainedIn(cU, nD.scope) , isStrictlyContainedIn(nD.scope, cS) }; @@ -136,7 +133,7 @@ set[IllegalRenameReason] checkCausesCaptures(TModel tm, start[Module] m, set[Def // Will this rename hide a used definition of `newName` behind a definition of `oldName` (shadowing)? set[Capture] newUseShadowedByRename = { | Define nD <- newDefs - , nU <- invert(tm.useDef)[newDefs.defined] + , nU <- invert(ws.useDef)[newDefs.defined] , Define cD <- currentDefs , isContainedIn(cD.scope, nD.scope) , isContainedIn(nU, cD.scope) @@ -150,20 +147,17 @@ set[IllegalRenameReason] checkCausesCaptures(TModel tm, start[Module] m, set[Def return allCaptures == {} ? {} : {captureChange(allCaptures)}; } -void checkLegalRename(TModel tm, start[Module] m, loc cursorLoc, set[Define] currentDefs, set[loc] currentUses, str newName) { - set[Define] newNameDefs = {def | def:<_, newName, _, _, _, _> <- tm.defines}; +set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, str newName) { + set[Define] newNameDefs = {def | def:<_, newName, _, _, _, _> <- ws.defines}; checkUnsupported(m.src, currentDefs); - set[IllegalRenameReason] reasons = + return reasons = checkLegalName(newName) - + checkCausesDoubleDeclarations(tm, currentDefs, newNameDefs) - + checkCausesCaptures(tm, m, currentDefs, currentUses, newNameDefs) + + checkDefinitionsOutsideWorkspace(ws, currentDefs.defined) + + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs) + + checkCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs) ; - - if (reasons != {}) { - throw illegalRename(cursorLoc, reasons); - } } void checkUnsupported(loc moduleLoc, set[Define] defsToRename) { @@ -176,9 +170,9 @@ void checkUnsupported(loc moduleLoc, set[Define] defsToRename) { str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; -// Find the smallest declaration that contains `nameLoc` -loc findDeclarationAroundName(TModel tm, loc nameLoc) = - (|unknown:///| | l < it && isContainedIn(nameLoc, l) ? l : it | l <- tm.defines.defined); +// TODO Move to stdlib? +loc findSmallestContaining(set[loc] wrappers, loc l) = + (|unknown:///| | w < it && isContainedIn(l, w) ? w : it | w <- wrappers); // Find the smallest trees of defined non-terminal type with a source location in `useDefs` set[loc] findNames(start[Module] m, set[loc] useDefs) { @@ -203,44 +197,49 @@ Maybe[loc] locationOfName(QualifiedName qn) = just((qn.names[-1]).src); Maybe[loc] locationOfName(FunctionDeclaration f) = just(f.signature.name.src); default Maybe[loc] locationOfName(Tree t) = nothing(); -list[DocumentEdit] renameRascalSymbol(start[Module] m, Tree cursor, set[loc] workspaceFolders, TModel tm, str newName) { - loc cursorLoc = cursor.src; - set[Define] defs = {}; - if (cursorLoc in tm.useDef<0>) { - // Cursor is at a use - defs = getDefines(tm, cursorLoc); - } else { - // Cursor is at a name within a declaration - loc cursorAtDef = findDeclarationAroundName(tm, cursorLoc); - defs = tm.definitions[cursorAtDef] + getDefines(tm, cursorAtDef); +tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[Define] defs, set[loc] uses, str name) { + if (reasons := collectIllegalRenames(ws, m, defs, uses, name), reasons != {}) { + return ; } - set[loc] uses = ({} | it + getUses(tm, def) | def <- defs.defined); + replaceName = escapeName(name); + return <{}, [replace(l, replaceName) | l <- findNames(m, defs.defined + uses)]>; +} + +tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[Define] defs, set[loc] uses, str name) = + computeTextEdits(ws, parseModuleWithSpaces(moduleLoc), defs, uses, name); + +list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, set[Define] defs, set[loc] uses, str name) { + rel[loc file, Define defines] defsPerFile = { | d <- defs}; + rel[loc file, loc uses] usesPerFile = { | u <- uses}; - checkLegalRename(tm, m, cursorLoc, defs, uses, newName); + files = defsPerFile.file + usesPerFile.file; + map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults = + (file: | file <- files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], name)); + + if (reasons := union({moduleResults[file].reasons | file <- moduleResults}), reasons != {}) { + throw illegalRename(cursor, reasons); + } + + list[DocumentEdit] changes = [changed(file, moduleResults[file].edits) | file <- moduleResults]; - rel[loc file, loc useDefs] useDefsPerFile = { | loc useDef <- uses + defs}; - list[DocumentEdit] changes = [changed(file, [replace(nameLoc, escapeName(newName)) | nameLoc <- findNames(m, useDefsPerFile[file])]) | loc file <- useDefsPerFile.file];; // TODO If the cursor was a module name, we need to rename files as well list[DocumentEdit] renames = []; return changes + renames; } -// Compute the edits for the complete workspace here -list[DocumentEdit] renameRascalSymbol(start[Module] m, Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { - str moduleName = getModuleName(m.src.top, pcfg); +list[DocumentEdit] renameRascalSymbol(start[Module] _, Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { + loc cursorLoc = cursor.src; + + WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, pcfg); - ModuleStatus ms = rascalTModelForLocs([m.src.top], getRascalCoreCompilerConfig(pcfg), dummy_compile1); + loc useDefAtCursor = findSmallestContaining(ws.defines.defined + ws.useDef<0>, cursorLoc); + set[Define] defs = getDefines(ws, useDefAtCursor); + set[loc] uses = ({} | it + getUses(ws, def) | def <- defs.defined); - TModel tm = tmodel(); - try - tm = convertTModel2PhysicalLocs(ms.tmodels[moduleName]); - catch NoSuchKey(): { - throw unsupportedRename({}); - } - return renameRascalSymbol(m, cursor, workspaceFolders, tm, newName); + return computeDocumentEdits(ws, cursorLoc, defs, uses, newName); } //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc new file mode 100644 index 000000000..6d2d7aa27 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc @@ -0,0 +1,53 @@ +module lang::rascal::lsp::WorkspaceInfo + +import IO; +import Relation; + +import analysis::typepal::TModel; + +import lang::rascalcore::check::Checker; + +import util::FileSystem; +import util::Reflective; + +data WorkspaceInfo ( + rel[loc use, loc def] useDef = {}, + set[Define] defines = {}, + set[loc] modules = {} +) = workspaceInfo(set[loc] folders, PathConfig pcfg); + +private WorkspaceInfo loadModule(WorkspaceInfo ws, loc l) { + WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { + ws.useDef += tm.useDef; + ws.defines += tm.defines; + + return ws; + } + + moduleLoc = l.top; + + if (moduleLoc notin ws.modules) { + moduleName = getModuleName(moduleLoc, ws.pcfg); + + // Only check one module at a time, to limit the amount of memory used + ms = rascalTModelForLocs([moduleLoc], getRascalCoreCompilerConfig(ws.pcfg), dummy_compile1); + + ws = loadModel(ws, ms.tmodels[moduleName]); + ws.modules += { moduleLoc }; + } + + return ws; +} + +WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { + ws = workspaceInfo(folders, pcfg); + for (f <- folders, m <- find(f, "rsc")) { + ws = loadModule(ws, m); + } + return ws; +} + +set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; +set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; +set[Define] getDefines(WorkspaceInfo ws, set[loc] defs) = {def | d <- defs, def:<_, _, _, _, d, _> <- ws.defines}; +set[Define] getDefines(WorkspaceInfo ws, loc useDef) = getDefines(ws, getDefs(ws, useDef) + useDef); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index a7b7836b2..06c591a7b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -375,7 +375,6 @@ test bool renameParamToUsedConstructorName() = testRename( test bool renameToReservedName() { edits = getEdits("int foo = 8;", 0, "foo", "int"); - newNames = {name | e <- edits, changed(_, replaces) := e , r <- replaces, replace(_, name) := r}; @@ -428,7 +427,7 @@ list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str // Test renaming given a module Tree and rename parameters list[DocumentEdit] getEdits(start[Module] m, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(m, cursor, {}, pcfg, newName); + return renameRascalSymbol(m, cursor, {src | src <- pcfg.srcs}, pcfg, newName); } tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "", str imports = "") { @@ -443,7 +442,13 @@ tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNa // Write the file to disk (and clean up later) to easily emulate typical editor behaviour loc moduleFileName = |memory://tests/rename/src/.rsc|; writeFile(moduleFileName, moduleStr); - list[DocumentEdit] edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName); + list[DocumentEdit] edits = []; + try { + edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName); + } catch e: { + remove(moduleFileName); + throw e; + } return ; } @@ -493,7 +498,7 @@ set[int] extractRenameOccurrences(start[Module] m, list[DocumentEdit] edits, str list[DocumentEdit] multiModuleTest() { PathConfig pcfg = pathConfig( - bin=|memory://tests/rename/bin|, + bin=resolveLocation(|project://rascal-vscode-extension/test-workspace/test-project/target/classes|), libs=[|lib://rascal| , resolveLocation(|project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal/|)], srcs=[resolveLocation(|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal/|) From 2706efb349b14677a7fd4517695da360213d3b22 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 21 Jun 2024 14:59:32 +0200 Subject: [PATCH 002/105] Support global functions. --- .../main/rascal/lang/rascal/lsp/Rename.rsc | 19 +++---------------- .../main/rascal/lang/rascal/tests/Rename.rsc | 17 ++++++++++------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc index 387ff7a84..1a209f68c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc @@ -76,12 +76,6 @@ data RenameException | unexpectedFailure(str message) ; -void throwIfNotEmpty(&T(&A) R, &A arg) { - if (arg != {}) { - throw R(arg); - } -} - set[IllegalRenameReason] checkLegalName(str name) { try { parse(#Name, escapeName(name)); @@ -150,8 +144,6 @@ set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, str newName) { set[Define] newNameDefs = {def | def:<_, newName, _, _, _, _> <- ws.defines}; - checkUnsupported(m.src, currentDefs); - return reasons = checkLegalName(newName) + checkDefinitionsOutsideWorkspace(ws, currentDefs.defined) @@ -160,14 +152,6 @@ set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m ; } -void checkUnsupported(loc moduleLoc, set[Define] defsToRename) { - Maybe[str] isUnsupportedDefinition(Define _: ) = just("Global function definitions might span multiple modules; unsupported for now.") - when moduleLoc == scope; // function is defined in module scope - default Maybe[str] isUnsupportedDefinition(Define _) = nothing(); - - throwIfNotEmpty(unsupportedRename, { | def <- defsToRename, just(msg) := isUnsupportedDefinition(def)}); -} - str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; // TODO Move to stdlib? @@ -195,6 +179,9 @@ set[loc] findNames(start[Module] m, set[loc] useDefs) { Maybe[loc] locationOfName(Name n) = just(n.src); Maybe[loc] locationOfName(QualifiedName qn) = just((qn.names[-1]).src); Maybe[loc] locationOfName(FunctionDeclaration f) = just(f.signature.name.src); +Maybe[loc] locationOfName(Variable v) = just(v.name.src); +Maybe[loc] locationOfName(Declaration d) = just(d.name.src) when d is annotation + || d is \tag; default Maybe[loc] locationOfName(Tree t) = nothing(); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index 06c591a7b..53c800052 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -246,7 +246,6 @@ test bool nestedRecursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences( 'fib(7); ", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); -@expected{unsupportedRename} test bool recursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences("fib(7);", decls = " 'int fib(int n) { ' switch (n) { @@ -302,22 +301,19 @@ test bool innerNestedFunctionParameter() = {1, 2} == testRenameOccurrences(" '} ", cursorAtOldNameOccurrence = 1); -@expected{unsupportedRename} -test bool publicFunction() = testRename("foo(1);", decls = " +test bool publicFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " 'public int foo(int f) { ' return f; '} "); -@expected{unsupportedRename} -test bool defaultFunction() = testRename("", decls = " +test bool defaultFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " 'int foo(int f) { ' return f; '} "); -@expected{unsupportedRename} -test bool privateFunction() = testRename("", decls = " +test bool privateFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " 'private int foo(int f) { ' return f; '} @@ -367,6 +363,13 @@ test bool renameParamToConstructorName() = {0, 1} == testRenameOccurrences( decls = "data Bar = bar();" ); +test bool globalVar() = {0, 3} == testRenameOccurrences(" + 'int f(int foo) = foo; + 'foo = 16; +", decls = " + 'int foo = 8; +"); + @expected{illegalRename} test bool renameParamToUsedConstructorName() = testRename( "Bar f(int foo) = bar(foo);", From d22b3f34c1341ef5d2f7be300b92ea6f3bb28f35 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 21 Jun 2024 15:23:18 +0200 Subject: [PATCH 003/105] Do not pass module tree to renaming. --- .../vscode/lsp/rascal/RascalLanguageServices.java | 2 +- rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc | 2 +- rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java index 20af37762..0d1cdc2bb 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java @@ -183,7 +183,7 @@ public InterruptibleFuture getRename(ITree module, Position cursor, Set (IList) eval.call("renameRascalSymbol", module, cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), addResources(pcfg), VF.string(newName)), + return runEvaluator("Rascal rename", semanticEvaluator, eval -> (IList) eval.call("renameRascalSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), addResources(pcfg), VF.string(newName)), VF.list(), exec); } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc index 1a209f68c..3a29509fb 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc @@ -217,7 +217,7 @@ list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, set[Define return changes + renames; } -list[DocumentEdit] renameRascalSymbol(start[Module] _, Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { +list[DocumentEdit] renameRascalSymbol(Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { loc cursorLoc = cursor.src; WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, pcfg); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index 53c800052..e6270320f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -430,7 +430,7 @@ list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str // Test renaming given a module Tree and rename parameters list[DocumentEdit] getEdits(start[Module] m, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(m, cursor, {src | src <- pcfg.srcs}, pcfg, newName); + return renameRascalSymbol(cursor, {src | src <- pcfg.srcs}, pcfg, newName); } tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "", str imports = "") { @@ -464,8 +464,7 @@ list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str old set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); - start[Module] m = parseModuleWithSpaces(moduleFileName); - occs = extractRenameOccurrences(m, edits, oldName); + occs = extractRenameOccurrences(moduleFileName, edits, oldName); remove(moduleFileName); return occs; } @@ -480,7 +479,8 @@ bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = " return false; } -set[int] extractRenameOccurrences(start[Module] m, list[DocumentEdit] edits, str name) { +set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { + start[Module] m = parseModuleWithSpaces(moduleFileName); list[loc] oldNameOccurrences = []; for (/Name n := m, "" == name) { oldNameOccurrences += n.src; From 5d8f1dba74859c95f70b2651b023bd22f9fc80b6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 27 Jun 2024 14:29:43 +0200 Subject: [PATCH 004/105] Multi-module test setup. --- .../main/rascal/lang/rascal/tests/Rename.rsc | 107 ++++++++++++++++-- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index e6270320f..f559f1a2f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -30,6 +30,8 @@ import lang::rascal::lsp::Rename; import Exception; import IO; +import List; +import Message; import String; import lang::rascal::\syntax::Rascal; @@ -402,6 +404,24 @@ test bool newNameHasNumericPrefix() = testRename("int foo = 8;", newName = "8abc @expected{illegalRename} test bool newNameIsEscapedInvalid() = testRename("int foo = 8;", newName = "\\8int"); +test bool multiModuleTest() = testRenameOccurrences(( + "Fib": <"int foo = 8; + ' + 'int fib(int n) { + ' if (n \< 2) { + ' return 1; + ' } + ' return fib(n - 1) + fib(n -2); + '}" + , {0}> + , "Main": <"import Fib; + ' + 'int main() { + ' fib(Fib::foo); + ' return 0; + '}" + , {0}> + ), <"Main", "foo", 0>); //// Fixtures and utility functions @@ -499,15 +519,84 @@ set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, } } -list[DocumentEdit] multiModuleTest() { - PathConfig pcfg = pathConfig( - bin=resolveLocation(|project://rascal-vscode-extension/test-workspace/test-project/target/classes|), - libs=[|lib://rascal| - , resolveLocation(|project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal/|)], - srcs=[resolveLocation(|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal/|) - , resolveLocation(|project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal/|)] +set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { + start[Module] m = parseModuleWithSpaces(moduleFileName); + list[loc] oldNameOccurrences = []; + for (/Name n := m, "" == name) { + oldNameOccurrences += n.src; + } + + if ([changed(_, replaces)] := edits) { + idx = {indexOf(oldNameOccurrences, l) | replace(l, _) <- replaces}; + return idx; + } else { + throw "Unexpected changes: "; + } +} + +private PathConfig getMultiModuleConfig(loc testDir) { + return pathConfig( + bin=testDir + "bin", + libs=[|lib://rascal|], + srcs=[testDir + "rascal"], + resources=|memory://tests/rename/resources|, + generatedSources=|memory://tests/rename/generated-sources| ); +} + +str moduleNameToPath(str name) = replaceAll(name, "::", "/"); +str modulePathToName(str path) = replaceAll(path, "/", "::"); + +Tree findCursor(loc f, str id, int occ) = [n | m := parseModuleWithSpaces(f), /Name n := m.top, "" == id][occ]; + +loc storeTestModule(loc dir, str name, str body) { + str moduleStr = " + 'module + ' + "; + + loc moduleFile = dir + "rascal" + (moduleNameToPath(name) + ".rsc"); + writeFile(moduleFile, moduleStr); + + return moduleFile; +} + +set[Tree] occsToTrees(start[Module] m, str name, set[int] occs) = {n | i <- occs, n := [n | /Name n := m.top, "" == name][i]}; +set[loc] occsToLocs(start[Module] m, str name, set[int] occs) = {t.src | t <- occsToTrees(m, name, occs)}; +set[int] locsToOccs(start[Module] m, str name, set[loc] occs) = {indexOf(names, occ) | names := [n.src | /Name n := m.top, "" == name], occ <- occs}; + +void checkNoErrors(list[ModuleMessages] msgs) { + if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) + throw errors; +} + +alias TestModule = tuple[str body, set[int] expectedRenameOccs]; +bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { + str testName = "Test"; + loc testDir = |memory://tests/rename/|; + + PathConfig pcfg = getMultiModuleConfig(testDir); + + Tree cursorT = char(0); + for (name <- modules) { + m = modules[name]; + l = storeTestModule(testDir, name, m.body); + if (cursor.moduleName == name) { + cursorT = findCursor(l, cursor.id, cursor.occ); + } + } + + checkNoErrors(checkAll(testDir, getRascalCoreCompilerConfig(pcfg))); + + edits = renameRascalSymbol(cursorT, {testDir}, pcfg, newName); + + editsPerModule = (name: locsToOccs(m, cursor.id, locs) | changed(f, textedits) <- edits + , start[Module] m := parseModuleWithSpaces(f) + , str name := getModuleName(f, pcfg) + , set[loc] locs := {l | replace(l, _) <- textedits}); + expectedEditsPerModule = (name: modules[name].expectedRenameOccs | name <- modules); + + remove(testDir); - return getEdits(resolveLocation(|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal/LibCall.rsc|) - , 0, "fib", "fibonacci", pcfg=pcfg); + return editsPerModule == expectedEditsPerModule; } From 401ccfc02963992ce5e4c2445496e3db687fe5a0 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 2 Jul 2024 15:54:36 +0200 Subject: [PATCH 005/105] Add large, repetitive test. --- rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index f559f1a2f..0edbd4435 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -423,6 +423,14 @@ test bool multiModuleTest() = testRenameOccurrences(( , {0}> ), <"Main", "foo", 0>); +int LARGE_TEST_SIZE = 200; +test bool largeTest() = ({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LARGE_TEST_SIZE], foos := 5 * i) == testRenameOccurrences(( + "int foo = 8;" + | " + 'int f(int foo) = foo; + 'foo = foo + foo;" + | i <- [0..LARGE_TEST_SIZE]) +); //// Fixtures and utility functions From d64376c0868166cdae9d8fd96d97f2d588a84120 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 2 Jul 2024 15:55:05 +0200 Subject: [PATCH 006/105] Extract test occurrences faster. --- rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index 0edbd4435..7bfaed1cf 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -515,13 +515,8 @@ set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, } if ([changed(_, replaces)] := edits) { - idx = {}; - for (replace(l, _) <- replaces, i := indexOf(oldNameOccurrences, l)) { - if (i == -1) throw "Cannot find in "; - idx += i; - } - - return idx; + repls = {l | replace(l, _) <- replaces}; + return {i | i <- [0..size(oldNameOccurrences)], oldNameOccurrences[i] in repls};; } else { throw "Unexpected changes: "; } From 3df2d129809de0187e487b3c788678429b134d68 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 2 Jul 2024 17:25:36 +0200 Subject: [PATCH 007/105] Support aliases, data types. --- .../main/rascal/lang/rascal/lsp/Rename.rsc | 4 ++ .../main/rascal/lang/rascal/tests/Rename.rsc | 63 ++++++++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc index 3a29509fb..8b20a0d18 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc @@ -182,6 +182,10 @@ Maybe[loc] locationOfName(FunctionDeclaration f) = just(f.signature.name.src); Maybe[loc] locationOfName(Variable v) = just(v.name.src); Maybe[loc] locationOfName(Declaration d) = just(d.name.src) when d is annotation || d is \tag; +Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is \alias + || d is dataAbstract + || d is \data; + default Maybe[loc] locationOfName(Tree t) = nothing(); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index 7bfaed1cf..65f05342a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -365,13 +365,6 @@ test bool renameParamToConstructorName() = {0, 1} == testRenameOccurrences( decls = "data Bar = bar();" ); -test bool globalVar() = {0, 3} == testRenameOccurrences(" - 'int f(int foo) = foo; - 'foo = 16; -", decls = " - 'int foo = 8; -"); - @expected{illegalRename} test bool renameParamToUsedConstructorName() = testRename( "Bar f(int foo) = bar(foo);", @@ -404,7 +397,14 @@ test bool newNameHasNumericPrefix() = testRename("int foo = 8;", newName = "8abc @expected{illegalRename} test bool newNameIsEscapedInvalid() = testRename("int foo = 8;", newName = "\\8int"); -test bool multiModuleTest() = testRenameOccurrences(( +test bool globalVar() = {0, 3} == testRenameOccurrences(" + 'int f(int foo) = foo; + 'foo = 16; +", decls = " + 'int foo = 8; +"); + +test bool multiModuleVar() = testRenameOccurrences(( "Fib": <"int foo = 8; ' 'int fib(int n) { @@ -421,7 +421,52 @@ test bool multiModuleTest() = testRenameOccurrences(( ' return 0; '}" , {0}> - ), <"Main", "foo", 0>); + ), <"Main", "foo", 0>, newName = "Bar"); + +test bool globalAlias() = {0, 1, 2, 3} == testRenameOccurrences(" + 'Foo f(Foo x) = x; + 'Foo foo = 8; + 'y = f(foo); +", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar"); + +test bool multiModuleAlias() = testRenameOccurrences(( + "Fib": <"alias Foo = int; + 'Foo fib(int n) { + ' if (n \< 2) { + ' return 1; + ' } + ' return fib(n - 1) + fib(n -2); + '}" + , {0, 1}> + , "Main": <"import Fib; + ' + 'int main() { + ' Foo result = fib(8); + ' return 0; + '}" + , {0}> + ), <"Main", "Foo", 0>, newName = "Bar"); + +test bool globalData() = {0, 1, 2, 3} == testRenameOccurrences(" + 'Foo f(Foo x) = x; + 'Foo x = foo(); + 'y = f(x); +", decls = "data Foo = foo();", oldName = "Foo", newName = "Bar"); + +test bool multiModuleData() = testRenameOccurrences(( + "values::Bool": <" + 'data Bool = t() | f(); + ' + 'Bool and(Bool l, Bool r) = r is t ? l : f; + '" + , {1, 2, 3, 4}> + , "Main": <"import values::Bool; + ' + 'void main() { + ' Bool b = and(t(), f()); + '}" + , {1}> + ), <"Main", "Bool", 1>, newName = "Boolean"); int LARGE_TEST_SIZE = 200; test bool largeTest() = ({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LARGE_TEST_SIZE], foos := 5 * i) == testRenameOccurrences(( From 69553904c86e951a31763c76eb9ca02d00e2d3f9 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 2 Jul 2024 17:26:11 +0200 Subject: [PATCH 008/105] Support multi-module tests from disk as well. --- .../main/rascal/lang/rascal/tests/Rename.rsc | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index 65f05342a..eed44fba2 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -31,7 +31,9 @@ import lang::rascal::lsp::Rename; import Exception; import IO; import List; +import Map; import Message; +import Set; import String; import lang::rascal::\syntax::Rascal; @@ -592,6 +594,17 @@ private PathConfig getMultiModuleConfig(loc testDir) { ); } +private PathConfig getTestWorkspaceConfig() { + return pathConfig( + bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, + libs=[|lib://rascal|], + srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main| + , |project://rascal-vscode-extension/test-workspace/test-lib/src/main|], + resources=|memory://tests/rename/resources|, + generatedSources=|memory://tests/rename/generatedSources| + ); +} + str moduleNameToPath(str name) = replaceAll(name, "::", "/"); str modulePathToName(str path) = replaceAll(path, "/", "::"); @@ -625,26 +638,25 @@ bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, s PathConfig pcfg = getMultiModuleConfig(testDir); - Tree cursorT = char(0); - for (name <- modules) { - m = modules[name]; - l = storeTestModule(testDir, name, m.body); - if (cursor.moduleName == name) { - cursorT = findCursor(l, cursor.id, cursor.occ); - } - } + map[loc file, set[int] expectedRenameOccs] modulesByLocation = (l: m.expectedRenameOccs | name <- modules, m := modules[name], l := storeTestModule(testDir, name, m.body)); - checkNoErrors(checkAll(testDir, getRascalCoreCompilerConfig(pcfg))); + res = testRenameOccurrences(modulesByLocation, cursor, newName=newName, pcfg=pcfg); + remove(testDir); + return res; +} + +bool testRenameOccurrences(map[loc file, set[int] expectedRenameOccs] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar", PathConfig pcfg = getTestWorkspaceConfig()) { + checkNoErrors(check(toList(domain(modules)), rascalCompilerConfig(pcfg))); - edits = renameRascalSymbol(cursorT, {testDir}, pcfg, newName); + Tree cursorT = findCursor([l | l <- modules, getModuleName(l, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); + + edits = renameRascalSymbol(cursorT, {d| d <- pcfg.srcs}, pcfg, newName); editsPerModule = (name: locsToOccs(m, cursor.id, locs) | changed(f, textedits) <- edits , start[Module] m := parseModuleWithSpaces(f) , str name := getModuleName(f, pcfg) , set[loc] locs := {l | replace(l, _) <- textedits}); - expectedEditsPerModule = (name: modules[name].expectedRenameOccs | name <- modules); - - remove(testDir); + expectedEditsPerModule = (name: modules[l] | l <- modules, name := getModuleName(l, pcfg)); return editsPerModule == expectedEditsPerModule; } From 6f67fcecb321e0f6deedaa807f8e9da96f5030d0 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 3 Jul 2024 09:50:59 +0200 Subject: [PATCH 009/105] Test qualified names in multi-module tests. --- .../src/main/rascal/lang/rascal/tests/Rename.rsc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc index eed44fba2..896190b0a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc @@ -407,7 +407,7 @@ test bool globalVar() = {0, 3} == testRenameOccurrences(" "); test bool multiModuleVar() = testRenameOccurrences(( - "Fib": <"int foo = 8; + "alg::Fib": <"int foo = 8; ' 'int fib(int n) { ' if (n \< 2) { @@ -416,10 +416,10 @@ test bool multiModuleVar() = testRenameOccurrences(( ' return fib(n - 1) + fib(n -2); '}" , {0}> - , "Main": <"import Fib; + , "Main": <"import alg::Fib; ' 'int main() { - ' fib(Fib::foo); + ' fib(alg::Fib::foo); ' return 0; '}" , {0}> @@ -432,7 +432,7 @@ test bool globalAlias() = {0, 1, 2, 3} == testRenameOccurrences(" ", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar"); test bool multiModuleAlias() = testRenameOccurrences(( - "Fib": <"alias Foo = int; + "alg::Fib": <"alias Foo = int; 'Foo fib(int n) { ' if (n \< 2) { ' return 1; @@ -440,7 +440,7 @@ test bool multiModuleAlias() = testRenameOccurrences(( ' return fib(n - 1) + fib(n -2); '}" , {0, 1}> - , "Main": <"import Fib; + , "Main": <"import alg::Fib; ' 'int main() { ' Foo result = fib(8); From fe43110e47b33e017c68656d5c8b27e7dfc6d53d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 3 Jul 2024 14:21:48 +0200 Subject: [PATCH 010/105] Split tests over multiple files. --- .../lang/rascal/lsp/refactor/Exception.rsc | 16 + .../lang/rascal/lsp/{ => refactor}/Rename.rsc | 43 +- .../lsp/{ => refactor}/WorkspaceInfo.rsc | 2 +- .../main/rascal/lang/rascal/tests/Rename.rsc | 662 ------------------ .../lang/rascal/tests/rename/Fields.rsc | 4 + .../rascal/tests/rename/FormalParameters.rsc | 121 ++++ .../lang/rascal/tests/rename/Functions.rsc | 83 +++ .../lang/rascal/tests/rename/Performance.rsc | 12 + .../lang/rascal/tests/rename/TestUtils.rsc | 229 ++++++ .../rascal/lang/rascal/tests/rename/Types.rsc | 48 ++ .../lang/rascal/tests/rename/ValidNames.rsc | 30 + .../lang/rascal/tests/rename/Variables.rsc | 166 +++++ .../swat/rascal/lsp/util/RenameTests.java | 2 +- 13 files changed, 726 insertions(+), 692 deletions(-) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc rename rascal-lsp/src/main/rascal/lang/rascal/lsp/{ => refactor}/Rename.rsc (81%) rename rascal-lsp/src/main/rascal/lang/rascal/lsp/{ => refactor}/WorkspaceInfo.rsc (96%) delete mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc new file mode 100644 index 000000000..b88e730fa --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -0,0 +1,16 @@ +module lang::rascal::lsp::refactor::Exception + +alias Capture = tuple[loc def, loc use]; + +data IllegalRenameReason + = invalidName(str name) + | doubleDeclaration(loc old, set[loc] new) + | captureChange(set[Capture] captures) + | definitionsOutsideWorkspace(set[loc] defs) + ; + +data RenameException + = illegalRename(loc location, set[IllegalRenameReason] reason) + | unsupportedRename(rel[loc location, str message] issues) + | unexpectedFailure(str message) + ; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc similarity index 81% rename from rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc rename to rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 8b20a0d18..30af72cfd 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -25,7 +25,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } @bootstrapParser -module lang::rascal::lsp::Rename +module lang::rascal::lsp::refactor::Rename /** * Rename refactoring @@ -52,7 +52,8 @@ import lang::rascalcore::check::RascalConfig; import analysis::typepal::TypePal; -import lang::rascal::lsp::WorkspaceInfo; +import lang::rascal::lsp::refactor::Exception; +import lang::rascal::lsp::refactor::WorkspaceInfo; import analysis::diff::edits::TextEdits; @@ -61,21 +62,6 @@ import vis::Text; import util::Maybe; import util::Reflective; -alias Capture = tuple[loc def, loc use]; - -data IllegalRenameReason - = invalidName(str name) - | doubleDeclaration(loc old, set[loc] new) - | captureChange(set[Capture] captures) - | definitionsOutsideWorkspace(set[loc] defs) - ; - -data RenameException - = illegalRename(loc location, set[IllegalRenameReason] reason) - | unsupportedRename(rel[loc location, str message] issues) - | unexpectedFailure(str message) -; - set[IllegalRenameReason] checkLegalName(str name) { try { parse(#Name, escapeName(name)); @@ -85,9 +71,10 @@ set[IllegalRenameReason] checkLegalName(str name) { } } -set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = { definitionsOutsideWorkspace(d) | d <- groupRangeByDomain({ | d <- defs, f := d.top, f notin ws.modules}) }; +private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = + { definitionsOutsideWorkspace(d) | d <- groupRangeByDomain({ | d <- defs, f := d.top, f notin ws.modules}) }; -set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Define] currentDefs, set[Define] newDefs) { +private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Define] currentDefs, set[Define] newDefs) { // Is newName already resolvable from a scope where is currently declared? map[loc, Define] defMap = (def.defined: def | def <- ws.defines); rel[loc old, loc new] doubleDeclarations = { | Define cD <- currentDefs @@ -99,13 +86,13 @@ set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Def return {doubleDeclaration(old, doubleDeclarations[old]) | old <- doubleDeclarations.old}; } -set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { +private set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { set[loc] maybeImplicitDefs = {l | /QualifiedName n := m, just(l) := locationOfName(n)}; return {def | Define def <- newDefs, (def.idRole is variableId && def.defined in ws.useDef<0>) || (def.idRole is patternVariableId && def.defined in maybeImplicitDefs)}; } -set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, set[Define] newDefs) { +private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, set[Define] newDefs) { set[Define] newNameImplicitDefs = findImplicitDefinitions(ws, m, newDefs); set[Define] newNameExplicitDefs = newDefs - newNameImplicitDefs; @@ -141,7 +128,7 @@ set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, return allCaptures == {} ? {} : {captureChange(allCaptures)}; } -set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, str newName) { +private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, str newName) { set[Define] newNameDefs = {def | def:<_, newName, _, _, _, _> <- ws.defines}; return reasons = @@ -152,14 +139,14 @@ set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m ; } -str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; +private str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; // TODO Move to stdlib? -loc findSmallestContaining(set[loc] wrappers, loc l) = +private loc findSmallestContaining(set[loc] wrappers, loc l) = (|unknown:///| | w < it && isContainedIn(l, w) ? w : it | w <- wrappers); // Find the smallest trees of defined non-terminal type with a source location in `useDefs` -set[loc] findNames(start[Module] m, set[loc] useDefs) { +private set[loc] findNames(start[Module] m, set[loc] useDefs) { set[loc] names = {}; visit(m.top) { case t: appl(prod(_, _, _), _): { @@ -189,7 +176,7 @@ Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is default Maybe[loc] locationOfName(Tree t) = nothing(); -tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[Define] defs, set[loc] uses, str name) { +private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[Define] defs, set[loc] uses, str name) { if (reasons := collectIllegalRenames(ws, m, defs, uses, name), reasons != {}) { return ; } @@ -198,10 +185,10 @@ tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(W return <{}, [replace(l, replaceName) | l <- findNames(m, defs.defined + uses)]>; } -tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[Define] defs, set[loc] uses, str name) = +private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[Define] defs, set[loc] uses, str name) = computeTextEdits(ws, parseModuleWithSpaces(moduleLoc), defs, uses, name); -list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, set[Define] defs, set[loc] uses, str name) { +private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, set[Define] defs, set[loc] uses, str name) { rel[loc file, Define defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc similarity index 96% rename from rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc rename to rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 6d2d7aa27..8ff42aa9a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -1,4 +1,4 @@ -module lang::rascal::lsp::WorkspaceInfo +module lang::rascal::lsp::refactor::WorkspaceInfo import IO; import Relation; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc deleted file mode 100644 index 896190b0a..000000000 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/Rename.rsc +++ /dev/null @@ -1,662 +0,0 @@ -@license{ -Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::Rename - -import lang::rascal::lsp::Rename; - -import Exception; -import IO; -import List; -import Map; -import Message; -import Set; -import String; - -import lang::rascal::\syntax::Rascal; - -import lang::rascalcore::check::Checker; -import lang::rascalcore::check::BasicRascalConfig; -import lang::rascalcore::check::RascalConfig; - -import analysis::diff::edits::TextEdits; - -import util::Math; -import util::Reflective; - -test bool freshName() = {0} == testRenameOccurrences(" - 'int foo = 8; - 'int qux = 10; -"); - -test bool shadowVariableInInnerScope() = {0} == testRenameOccurrences(" - 'int foo = 8; - '{ - ' int bar = 9; - '} -"); - -test bool parameterShadowsVariable() = {0} == testRenameOccurrences(" - 'int foo = 8; - 'int f(int bar) { - ' return bar; - '} -"); - -@expected{illegalRename} -test bool implicitVariableDeclarationInSameScopeBecomesUse() = testRename(" - 'int foo = 8; - 'bar = 9; -"); - -@expected{illegalRename} -test bool implicitVariableDeclarationInInnerScopeBecomesUse() = testRename(" - 'int foo = 8; - '{ - ' bar = 9; - '} -"); - -@expected{illegalRename} -test bool doubleVariableDeclaration() = testRename(" - 'int foo = 8; - 'int bar = 9; -"); - -@expected{illegalRename} -test bool doubleVariableAndParameterDeclaration() = testRename(" - 'int f(int foo) { - ' int bar = 9; - ' return foo + bar; - '} -"); - -test bool adjacentScopes() = {0} == testRenameOccurrences(" - '{ - ' int foo = 8; - '} - '{ - ' int bar = 9; - '} -"); - -@expected{illegalRename} -test bool implicitPatterVariableInSameScopeBecomesUse() = testRename(" - 'int foo = 8; - 'bar := 9; -"); - -@expected{illegalRename} -test bool implicitNestedPatterVariableInSameScopeBecomesUse() = testRename(" - 'int foo = 8; - '\ := \<9, 99\>; -"); - -@expected{illegalRename} -test bool implicitPatterVariableInInnerScopeBecomesUse() = testRename(" - 'int foo = 8; - 'if (bar := 9) { - ' temp = 2 * bar; - '} -"); - -test bool explicitPatternVariableInInnerScope() = {0} == testRenameOccurrences(" - 'int foo = 8; - 'if (int bar := 9) { - ' bar = 2 * bar; - '} -"); - -test bool becomesPatternInInnerScope() = {0} == testRenameOccurrences(" - 'int foo = 8; - 'if (bar : int _ := 9) { - ' bar = 2 * bar; - '} -"); - -@expected{illegalRename} -test bool implicitPatternVariableBecomesInInnerScope() = testRename(" - 'int foo = 8; - 'if (bar : _ := 9) { - ' bar = 2 * foo; - '} -"); - -@expected{illegalRename} -test bool explicitPatternVariableBecomesInInnerScope() = testRename(" - 'int foo = 8; - 'if (bar : int _ := 9) { - ' bar = 2 * foo; - '} -"); - -@expected{illegalRename} -test bool shadowDeclaration() = testRename(" - 'int foo = 8; - 'if (int bar := 9) { - ' foo = 2 * bar; - '} -"); - -// Although this is fine statically, it will cause runtime errors when `bar` is called -// > A value of type int is not something you can call like a function, a constructor or a closure. -@expected{illegalRename} -test bool doubleVariableAndFunctionDeclaration() = testRename(" - 'int foo = 8; - 'void bar() {} -"); - -// Although this is fine statically, it will cause runtime errors when `bar` is called -// > A value of type int is not something you can call like a function, a constructor or a closure. -@expected{illegalRename} -test bool doubleFunctionAndVariableDeclaration() = testRename(" - 'void bar() {} - 'foo = 8; -"); - -@expected{illegalRename} -test bool doubleFunctionAndNestedVariableDeclaration() = testRename(" - 'bool bar() = true; - 'void f() { - ' int foo = 0; - '} -"); - -@expected{illegalRename} -test bool captureFunctionParameter() = testRename(" - 'int f(int foo) { - ' int bar = 9; - ' return foo + bar; - '} -"); - -test bool paremeterShadowsParameter1() = {0, 3} == testRenameOccurrences(" - 'int f1(int foo) { - ' int f2(int foo) { - ' int baz = 9; - ' return foo + baz; - ' } - ' return f2(foo); - '} -"); - -test bool paremeterShadowsParameter2() = {1, 2} == testRenameOccurrences(" - 'int f1(int foo) { - ' int f2(int foo) { - ' int baz = 9; - ' return foo + baz; - ' } - ' return f2(foo); - '} -", cursorAtOldNameOccurrence = 1); - -@expected{illegalRename} -test bool paremeterShadowsParameter3() = testRename(" - 'int f(int bar) { - ' int g(int baz) { - ' int h(int foo) { - ' return bar; - ' } - ' return h(baz); - ' } - ' return g(bar); - '} -"); - -test bool nestedFunctionParameter() = {0, 1} == testRenameOccurrences(" - 'int f(int foo, int baz) { - ' return foo; - '} -"); - -test bool nestedRecursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences(" - 'int fib(int n) { - ' switch (n) { - ' case 0: { - ' return 1; - ' } - ' case 1: { - ' return 1; - ' } - ' default: { - ' return fib(n - 1) + fib(n - 2); - ' } - ' } - '} - ' - 'fib(7); -", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); - -test bool recursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences("fib(7);", decls = " - 'int fib(int n) { - ' switch (n) { - ' case 0: { - ' return 1; - ' } - ' case 1: { - ' return 1; - ' } - ' default: { - ' return fib(n - 1) + fib(n - 2); - ' } - ' } - '} -", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); - -test bool nestedPublicFunction() = {0, 1} == testRenameOccurrences(" - 'public int foo(int f) { - ' return f; - '} - 'foo(1); -"); - -test bool nestedDefaultFunction() = {0, 1} == testRenameOccurrences(" - 'int foo(int f) { - ' return f; - '} - 'foo(1); -"); - -test bool nestedPrivateFunction() = {0, 1} == testRenameOccurrences(" - 'private int foo(int f) { - ' return f; - '} - 'foo(1); -"); - -test bool outerNestedFunctionParameter() = {0, 3} == testRenameOccurrences(" - 'int f(int foo) { - ' int g(int foo) { - ' return foo; - ' } - ' return f(foo); - '} -"); - -test bool innerNestedFunctionParameter() = {1, 2} == testRenameOccurrences(" - 'int f(int foo) { - ' int g(int foo) { - ' return foo; - ' } - ' return f(foo); - '} -", cursorAtOldNameOccurrence = 1); - -test bool publicFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " - 'public int foo(int f) { - ' return f; - '} -"); - -test bool defaultFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " - 'int foo(int f) { - ' return f; - '} -"); - -test bool privateFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " - 'private int foo(int f) { - ' return f; - '} -"); - -test bool publicFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " - 'public int f(int foo) { - ' return foo; - '} -"); - -test bool defaultFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " - 'int f(int foo) { - ' return foo; - '} -"); - -test bool privateFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " - 'private int f(int foo) { - ' return foo; - '} -"); - -@expected{unsupportedRename} -test bool nestedKeywordParameter() = {0, 1, 2} == testRenameOccurrences(" - 'int f(int foo = 8) = foo; - 'int x = f(foo = 10); -"); - -@expected{unsupportedRename} -test bool keywordParameter() = testRename( - "int x = f(foo = 10);" - decls="int f(int foo = 8) = foo;" -); - -@expected{illegalRename} test bool doubleParameterDeclaration1() = testRename("int f(int foo, int bar) = 1;"); -@expected{illegalRename} test bool doubleParameterDeclaration2() = testRename("int f(int bar, int foo) = 1;"); - -@expected{illegalRename} test bool doubleNormalAndKeywordParameterDeclaration1() = testRename("int f(int foo, int bar = 9) = 1;"); -@expected{illegalRename} test bool doubleNormalAndKeywordParameterDeclaration2() = testRename("int f(int bar, int foo = 8) = 1;"); - -@expected{illegalRename} test bool doubleKeywordParameterDeclaration1() = testRename("int f(int foo = 8, int bar = 9) = 1;"); -@expected{illegalRename} test bool doubleKeywordParameterDeclaration2() = testRename("int f(int bar = 9, int foo = 8) = 1;"); - -test bool renameParamToConstructorName() = {0, 1} == testRenameOccurrences( - "int f(int foo) = foo;", - decls = "data Bar = bar();" -); - -@expected{illegalRename} -test bool renameParamToUsedConstructorName() = testRename( - "Bar f(int foo) = bar(foo);", - decls = "data Bar = bar(int x);" -); - -test bool renameToReservedName() { - edits = getEdits("int foo = 8;", 0, "foo", "int"); - newNames = {name | e <- edits, changed(_, replaces) := e - , r <- replaces, replace(_, name) := r}; - - return newNames == {"\\int"}; -} - -@expected{illegalRename} -test bool renameToUsedReservedName() = testRename(" - 'int \\int = 0; - 'int foo = 8; -", newName = "int"); - -@expected{illegalRename} -test bool newNameIsNonAlphaNumeric() = testRename("int foo = 8;", newName = "b@r"); - -@expected{illegalRename} -test bool newNameIsNumber() = testRename("int foo = 8;", newName = "8"); - -@expected{illegalRename} -test bool newNameHasNumericPrefix() = testRename("int foo = 8;", newName = "8abc"); - -@expected{illegalRename} -test bool newNameIsEscapedInvalid() = testRename("int foo = 8;", newName = "\\8int"); - -test bool globalVar() = {0, 3} == testRenameOccurrences(" - 'int f(int foo) = foo; - 'foo = 16; -", decls = " - 'int foo = 8; -"); - -test bool multiModuleVar() = testRenameOccurrences(( - "alg::Fib": <"int foo = 8; - ' - 'int fib(int n) { - ' if (n \< 2) { - ' return 1; - ' } - ' return fib(n - 1) + fib(n -2); - '}" - , {0}> - , "Main": <"import alg::Fib; - ' - 'int main() { - ' fib(alg::Fib::foo); - ' return 0; - '}" - , {0}> - ), <"Main", "foo", 0>, newName = "Bar"); - -test bool globalAlias() = {0, 1, 2, 3} == testRenameOccurrences(" - 'Foo f(Foo x) = x; - 'Foo foo = 8; - 'y = f(foo); -", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar"); - -test bool multiModuleAlias() = testRenameOccurrences(( - "alg::Fib": <"alias Foo = int; - 'Foo fib(int n) { - ' if (n \< 2) { - ' return 1; - ' } - ' return fib(n - 1) + fib(n -2); - '}" - , {0, 1}> - , "Main": <"import alg::Fib; - ' - 'int main() { - ' Foo result = fib(8); - ' return 0; - '}" - , {0}> - ), <"Main", "Foo", 0>, newName = "Bar"); - -test bool globalData() = {0, 1, 2, 3} == testRenameOccurrences(" - 'Foo f(Foo x) = x; - 'Foo x = foo(); - 'y = f(x); -", decls = "data Foo = foo();", oldName = "Foo", newName = "Bar"); - -test bool multiModuleData() = testRenameOccurrences(( - "values::Bool": <" - 'data Bool = t() | f(); - ' - 'Bool and(Bool l, Bool r) = r is t ? l : f; - '" - , {1, 2, 3, 4}> - , "Main": <"import values::Bool; - ' - 'void main() { - ' Bool b = and(t(), f()); - '}" - , {1}> - ), <"Main", "Bool", 1>, newName = "Boolean"); - -int LARGE_TEST_SIZE = 200; -test bool largeTest() = ({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LARGE_TEST_SIZE], foos := 5 * i) == testRenameOccurrences(( - "int foo = 8;" - | " - 'int f(int foo) = foo; - 'foo = foo + foo;" - | i <- [0..LARGE_TEST_SIZE]) -); - -//// Fixtures and utility functions - -private PathConfig testPathConfig = pathConfig( - bin=|memory://tests/rename/bin|, - libs=[|lib://rascal|], - srcs=[|memory://tests/rename/src|], - resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generated-sources|); - -// Test renaming given the location of a module and rename parameters -list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { - void checkNoErrors(list[ModuleMessages] msgs) { - if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) - throw errors; - } - - loc f = resolveLocation(singleModule); - - checkNoErrors(checkAll(f, getRascalCoreCompilerConfig(pcfg))); - - return getEdits(parseModuleWithSpaces(f), cursorAtOldNameOccurrence, oldName, newName, pcfg=pcfg); -} - -// Test renaming given a module Tree and rename parameters -list[DocumentEdit] getEdits(start[Module] m, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { - Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(cursor, {src | src <- pcfg.srcs}, pcfg, newName); -} - -tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "", str imports = "") { - str moduleName = "TestModule"; - str moduleStr = - "module - ' - 'void main() { - ' - '}"; - - // Write the file to disk (and clean up later) to easily emulate typical editor behaviour - loc moduleFileName = |memory://tests/rename/src/.rsc|; - writeFile(moduleFileName, moduleStr); - list[DocumentEdit] edits = []; - try { - edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName); - } catch e: { - remove(moduleFileName); - throw e; - } - - return ; -} - -list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "") { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); - remove(l); - return edits; -} - -set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); - occs = extractRenameOccurrences(moduleFileName, edits, oldName); - remove(moduleFileName); - return occs; -} - -// Test renames that are expected to throw an exception -bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { - edits = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName decls=decls); - - print("UNEXPECTED EDITS: "); - iprintln(edits); - - return false; -} - -set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { - start[Module] m = parseModuleWithSpaces(moduleFileName); - list[loc] oldNameOccurrences = []; - for (/Name n := m, "" == name) { - oldNameOccurrences += n.src; - } - - if ([changed(_, replaces)] := edits) { - repls = {l | replace(l, _) <- replaces}; - return {i | i <- [0..size(oldNameOccurrences)], oldNameOccurrences[i] in repls};; - } else { - throw "Unexpected changes: "; - } -} - -set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { - start[Module] m = parseModuleWithSpaces(moduleFileName); - list[loc] oldNameOccurrences = []; - for (/Name n := m, "" == name) { - oldNameOccurrences += n.src; - } - - if ([changed(_, replaces)] := edits) { - idx = {indexOf(oldNameOccurrences, l) | replace(l, _) <- replaces}; - return idx; - } else { - throw "Unexpected changes: "; - } -} - -private PathConfig getMultiModuleConfig(loc testDir) { - return pathConfig( - bin=testDir + "bin", - libs=[|lib://rascal|], - srcs=[testDir + "rascal"], - resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generated-sources| - ); -} - -private PathConfig getTestWorkspaceConfig() { - return pathConfig( - bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, - libs=[|lib://rascal|], - srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main| - , |project://rascal-vscode-extension/test-workspace/test-lib/src/main|], - resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generatedSources| - ); -} - -str moduleNameToPath(str name) = replaceAll(name, "::", "/"); -str modulePathToName(str path) = replaceAll(path, "/", "::"); - -Tree findCursor(loc f, str id, int occ) = [n | m := parseModuleWithSpaces(f), /Name n := m.top, "" == id][occ]; - -loc storeTestModule(loc dir, str name, str body) { - str moduleStr = " - 'module - ' - "; - - loc moduleFile = dir + "rascal" + (moduleNameToPath(name) + ".rsc"); - writeFile(moduleFile, moduleStr); - - return moduleFile; -} - -set[Tree] occsToTrees(start[Module] m, str name, set[int] occs) = {n | i <- occs, n := [n | /Name n := m.top, "" == name][i]}; -set[loc] occsToLocs(start[Module] m, str name, set[int] occs) = {t.src | t <- occsToTrees(m, name, occs)}; -set[int] locsToOccs(start[Module] m, str name, set[loc] occs) = {indexOf(names, occ) | names := [n.src | /Name n := m.top, "" == name], occ <- occs}; - -void checkNoErrors(list[ModuleMessages] msgs) { - if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) - throw errors; -} - -alias TestModule = tuple[str body, set[int] expectedRenameOccs]; -bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { - str testName = "Test"; - loc testDir = |memory://tests/rename/|; - - PathConfig pcfg = getMultiModuleConfig(testDir); - - map[loc file, set[int] expectedRenameOccs] modulesByLocation = (l: m.expectedRenameOccs | name <- modules, m := modules[name], l := storeTestModule(testDir, name, m.body)); - - res = testRenameOccurrences(modulesByLocation, cursor, newName=newName, pcfg=pcfg); - remove(testDir); - return res; -} - -bool testRenameOccurrences(map[loc file, set[int] expectedRenameOccs] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar", PathConfig pcfg = getTestWorkspaceConfig()) { - checkNoErrors(check(toList(domain(modules)), rascalCompilerConfig(pcfg))); - - Tree cursorT = findCursor([l | l <- modules, getModuleName(l, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); - - edits = renameRascalSymbol(cursorT, {d| d <- pcfg.srcs}, pcfg, newName); - - editsPerModule = (name: locsToOccs(m, cursor.id, locs) | changed(f, textedits) <- edits - , start[Module] m := parseModuleWithSpaces(f) - , str name := getModuleName(f, pcfg) - , set[loc] locs := {l | replace(l, _) <- textedits}); - expectedEditsPerModule = (name: modules[l] | l <- modules, name := getModuleName(l, pcfg)); - - return editsPerModule == expectedEditsPerModule; -} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc new file mode 100644 index 000000000..29cb0a34f --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -0,0 +1,4 @@ +module lang::rascal::tests::rename::Fields + +// import lang::rascal::tests::rename::TestUtils; +// import lang::rascal::lsp::refactor::Exception; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc new file mode 100644 index 000000000..e77a42c12 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc @@ -0,0 +1,121 @@ +module lang::rascal::tests::rename::FormalParameters + +import lang::rascal::tests::rename::TestUtils; +import lang::rascal::lsp::refactor::Exception; + +test bool outerNestedFunctionParameter() = {0, 3} == testRenameOccurrences(" + 'int f(int foo) { + ' int g(int foo) { + ' return foo; + ' } + ' return f(foo); + '} +"); + +test bool innerNestedFunctionParameter() = {1, 2} == testRenameOccurrences(" + 'int f(int foo) { + ' int g(int foo) { + ' return foo; + ' } + ' return f(foo); + '} +", cursorAtOldNameOccurrence = 1); + +test bool publicFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " + 'public int f(int foo) { + ' return foo; + '} +"); + +test bool defaultFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " + 'int f(int foo) { + ' return foo; + '} +"); + +test bool privateFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " + 'private int f(int foo) { + ' return foo; + '} +"); + +@expected{unsupportedRename} +test bool nestedKeywordParameter() = {0, 1, 2} == testRenameOccurrences(" + 'int f(int foo = 8) = foo; + 'int x = f(foo = 10); +"); + +@expected{unsupportedRename} +test bool keywordParameter() = testRename( + "int x = f(foo = 10);" + decls="int f(int foo = 8) = foo;" +); + +@expected{illegalRename} test bool doubleParameterDeclaration1() = testRename("int f(int foo, int bar) = 1;"); +@expected{illegalRename} test bool doubleParameterDeclaration2() = testRename("int f(int bar, int foo) = 1;"); + +@expected{illegalRename} test bool doubleNormalAndKeywordParameterDeclaration1() = testRename("int f(int foo, int bar = 9) = 1;"); +@expected{illegalRename} test bool doubleNormalAndKeywordParameterDeclaration2() = testRename("int f(int bar, int foo = 8) = 1;"); + +@expected{illegalRename} test bool doubleKeywordParameterDeclaration1() = testRename("int f(int foo = 8, int bar = 9) = 1;"); +@expected{illegalRename} test bool doubleKeywordParameterDeclaration2() = testRename("int f(int bar = 9, int foo = 8) = 1;"); + +test bool renameParamToConstructorName() = {0, 1} == testRenameOccurrences( + "int f(int foo) = foo;", + decls = "data Bar = bar();" +); + +@expected{illegalRename} +test bool renameParamToUsedConstructorName() = testRename( + "Bar f(int foo) = bar(foo);", + decls = "data Bar = bar(int x);" +); + +test bool paremeterShadowsParameter1() = {0, 3} == testRenameOccurrences(" + 'int f1(int foo) { + ' int f2(int foo) { + ' int baz = 9; + ' return foo + baz; + ' } + ' return f2(foo); + '} +"); + +test bool paremeterShadowsParameter2() = {1, 2} == testRenameOccurrences(" + 'int f1(int foo) { + ' int f2(int foo) { + ' int baz = 9; + ' return foo + baz; + ' } + ' return f2(foo); + '} +", cursorAtOldNameOccurrence = 1); + +@expected{illegalRename} +test bool paremeterShadowsParameter3() = testRename(" + 'int f(int bar) { + ' int g(int baz) { + ' int h(int foo) { + ' return bar; + ' } + ' return h(baz); + ' } + ' return g(bar); + '} +"); + +@expected{illegalRename} +test bool captureFunctionParameter() = testRename(" + 'int f(int foo) { + ' int bar = 9; + ' return foo + bar; + '} +"); + +@expected{illegalRename} +test bool doubleVariableAndParameterDeclaration() = testRename(" + 'int f(int foo) { + ' int bar = 9; + ' return foo + bar; + '} +"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc new file mode 100644 index 000000000..929493e79 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -0,0 +1,83 @@ +module lang::rascal::tests::rename::Functions + +import lang::rascal::tests::rename::TestUtils; +// import lang::rascal::lsp::refactor::Exception; + +test bool nestedFunctionParameter() = {0, 1} == testRenameOccurrences(" + 'int f(int foo, int baz) { + ' return foo; + '} +"); + +test bool nestedRecursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences(" + 'int fib(int n) { + ' switch (n) { + ' case 0: { + ' return 1; + ' } + ' case 1: { + ' return 1; + ' } + ' default: { + ' return fib(n - 1) + fib(n - 2); + ' } + ' } + '} + ' + 'fib(7); +", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); + +test bool recursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences("fib(7);", decls = " + 'int fib(int n) { + ' switch (n) { + ' case 0: { + ' return 1; + ' } + ' case 1: { + ' return 1; + ' } + ' default: { + ' return fib(n - 1) + fib(n - 2); + ' } + ' } + '} +", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); + +test bool nestedPublicFunction() = {0, 1} == testRenameOccurrences(" + 'public int foo(int f) { + ' return f; + '} + 'foo(1); +"); + +test bool nestedDefaultFunction() = {0, 1} == testRenameOccurrences(" + 'int foo(int f) { + ' return f; + '} + 'foo(1); +"); + +test bool nestedPrivateFunction() = {0, 1} == testRenameOccurrences(" + 'private int foo(int f) { + ' return f; + '} + 'foo(1); +"); + +test bool publicFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " + 'public int foo(int f) { + ' return f; + '} +"); + +test bool defaultFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " + 'int foo(int f) { + ' return f; + '} +"); + +test bool privateFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " + 'private int foo(int f) { + ' return f; + '} +"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc new file mode 100644 index 000000000..8a26a532c --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc @@ -0,0 +1,12 @@ +module lang::rascal::tests::rename::Performance + +import lang::rascal::tests::rename::TestUtils; + +int LARGE_TEST_SIZE = 200; +test bool largeTest() = ({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LARGE_TEST_SIZE], foos := 5 * i) == testRenameOccurrences(( + "int foo = 8;" + | " + 'int f(int foo) = foo; + 'foo = foo + foo;" + | i <- [0..LARGE_TEST_SIZE]) +); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc new file mode 100644 index 000000000..93f5d2952 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -0,0 +1,229 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::TestUtils + +import lang::rascal::lsp::refactor::Rename; // Module under test + +import IO; +import Map; +import Message; +import Set; +import String; + +import lang::rascal::\syntax::Rascal; // `Name` + +import lang::rascalcore::check::Checker; +import lang::rascalcore::check::RascalConfig; + +import analysis::diff::edits::TextEdits; + +import util::Math; +import util::Reflective; + + +//// Fixtures and utility functions +alias TestModule = tuple[str body, set[int] expectedRenameOccs]; +bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { + str testName = "Test"; + loc testDir = |memory://tests/rename/|; + + PathConfig pcfg = getMultiModuleConfig(testDir); + + map[loc file, set[int] expectedRenameOccs] modulesByLocation = (l: m.expectedRenameOccs | name <- modules, m := modules[name], l := storeTestModule(testDir, name, m.body)); + + res = testRenameOccurrences(modulesByLocation, cursor, newName=newName, pcfg=pcfg); + remove(testDir); + return res; +} + +bool testRenameOccurrences(map[loc file, set[int] expectedRenameOccs] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar", PathConfig pcfg = getTestWorkspaceConfig()) { + checkNoErrors(check(toList(domain(modules)), rascalCompilerConfig(pcfg))); + + Tree cursorT = findCursor([l | l <- modules, getModuleName(l, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); + + edits = renameRascalSymbol(cursorT, {d| d <- pcfg.srcs}, pcfg, newName); + + editsPerModule = (name: locsToOccs(m, cursor.id, locs) | changed(f, textedits) <- edits + , start[Module] m := parseModuleWithSpaces(f) + , str name := getModuleName(f, pcfg) + , set[loc] locs := {l | replace(l, _) <- textedits}); + expectedEditsPerModule = (name: modules[l] | l <- modules, name := getModuleName(l, pcfg)); + + return editsPerModule == expectedEditsPerModule; +} + +set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); + occs = extractRenameOccurrences(moduleFileName, edits, oldName); + remove(moduleFileName); + return occs; +} + +list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "") { + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); + remove(l); + return edits; +} + +// Test renames that are expected to throw an exception +bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { + edits = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); + + print("UNEXPECTED EDITS: "); + iprintln(edits); + + return false; +} + + +private PathConfig testPathConfig = pathConfig( + bin=|memory://tests/rename/bin|, + libs=[|lib://rascal|], + srcs=[|memory://tests/rename/src|], + resources=|memory://tests/rename/resources|, + generatedSources=|memory://tests/rename/generated-sources|); + +private PathConfig getMultiModuleConfig(loc testDir) { + return pathConfig( + bin=testDir + "bin", + libs=[|lib://rascal|], + srcs=[testDir + "rascal"], + resources=|memory://tests/rename/resources|, + generatedSources=|memory://tests/rename/generated-sources| + ); +} + +private PathConfig getTestWorkspaceConfig() { + return pathConfig( + bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, + libs=[|lib://rascal|], + srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main| + , |project://rascal-vscode-extension/test-workspace/test-lib/src/main|], + resources=|memory://tests/rename/resources|, + generatedSources=|memory://tests/rename/generatedSources| + ); +} + +// Test renaming given the location of a module and rename parameters +list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { + void checkNoErrors(list[ModuleMessages] msgs) { + if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) + throw errors; + } + + loc f = resolveLocation(singleModule); + + checkNoErrors(checkAll(f, getRascalCoreCompilerConfig(pcfg))); + + return getEdits(parseModuleWithSpaces(f), cursorAtOldNameOccurrence, oldName, newName, pcfg=pcfg); +} + +// Test renaming given a module Tree and rename parameters +list[DocumentEdit] getEdits(start[Module] m, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { + Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; + return renameRascalSymbol(cursor, {src | src <- pcfg.srcs}, pcfg, newName); +} + +private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "", str imports = "") { + str moduleName = "TestModule"; + str moduleStr = + "module + ' + 'void main() { + ' + '}"; + + // Write the file to disk (and clean up later) to easily emulate typical editor behaviour + loc moduleFileName = |memory://tests/rename/src/.rsc|; + writeFile(moduleFileName, moduleStr); + list[DocumentEdit] edits = []; + try { + edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName); + } catch e: { + remove(moduleFileName); + throw e; + } + + return ; +} + + +private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { + start[Module] m = parseModuleWithSpaces(moduleFileName); + list[loc] oldNameOccurrences = []; + for (/Name n := m, "" == name) { + oldNameOccurrences += n.src; + } + + if ([changed(_, replaces)] := edits) { + repls = {l | replace(l, _) <- replaces}; + return {i | i <- [0..size(oldNameOccurrences)], oldNameOccurrences[i] in repls};; + } else { + throw "Unexpected changes: "; + } +} + +private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { + start[Module] m = parseModuleWithSpaces(moduleFileName); + list[loc] oldNameOccurrences = []; + for (/Name n := m, "" == name) { + oldNameOccurrences += n.src; + } + + if ([changed(_, replaces)] := edits) { + idx = {indexOf(oldNameOccurrences, l) | replace(l, _) <- replaces}; + return idx; + } else { + throw "Unexpected changes: "; + } +} + +private str moduleNameToPath(str name) = replaceAll(name, "::", "/"); +private str modulePathToName(str path) = replaceAll(path, "/", "::"); + +private Tree findCursor(loc f, str id, int occ) = [n | m := parseModuleWithSpaces(f), /Name n := m.top, "" == id][occ]; + +private loc storeTestModule(loc dir, str name, str body) { + str moduleStr = " + 'module + ' + "; + + loc moduleFile = dir + "rascal" + (moduleNameToPath(name) + ".rsc"); + writeFile(moduleFile, moduleStr); + + return moduleFile; +} + +private set[Tree] occsToTrees(start[Module] m, str name, set[int] occs) = {n | i <- occs, n := [n | /Name n := m.top, "" == name][i]}; +private set[loc] occsToLocs(start[Module] m, str name, set[int] occs) = {t.src | t <- occsToTrees(m, name, occs)}; +private set[int] locsToOccs(start[Module] m, str name, set[loc] occs) = {indexOf(names, occ) | names := [n.src | /Name n := m.top, "" == name], occ <- occs}; + +private void checkNoErrors(list[ModuleMessages] msgs) { + if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) + throw errors; +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc new file mode 100644 index 000000000..8f158c9a5 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -0,0 +1,48 @@ +module lang::rascal::tests::rename::Types + +import lang::rascal::tests::rename::TestUtils; + +test bool globalAlias() = {0, 1, 2, 3} == testRenameOccurrences(" + 'Foo f(Foo x) = x; + 'Foo foo = 8; + 'y = f(foo); +", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar"); + +test bool multiModuleAlias() = testRenameOccurrences(( + "alg::Fib": <"alias Foo = int; + 'Foo fib(int n) { + ' if (n \< 2) { + ' return 1; + ' } + ' return fib(n - 1) + fib(n -2); + '}" + , {0, 1}> + , "Main": <"import alg::Fib; + ' + 'int main() { + ' Foo result = fib(8); + ' return 0; + '}" + , {0}> + ), <"Main", "Foo", 0>, newName = "Bar"); + +test bool globalData() = {0, 1, 2, 3} == testRenameOccurrences(" + 'Foo f(Foo x) = x; + 'Foo x = foo(); + 'y = f(x); +", decls = "data Foo = foo();", oldName = "Foo", newName = "Bar"); + +test bool multiModuleData() = testRenameOccurrences(( + "values::Bool": <" + 'data Bool = t() | f(); + ' + 'Bool and(Bool l, Bool r) = r is t ? l : f; + '" + , {1, 2, 3, 4}> + , "Main": <"import values::Bool; + ' + 'void main() { + ' Bool b = and(t(), f()); + '}" + , {1}> + ), <"Main", "Bool", 1>, newName = "Boolean"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc new file mode 100644 index 000000000..72b351c38 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc @@ -0,0 +1,30 @@ +module lang::rascal::tests::rename::ValidNames + +import lang::rascal::tests::rename::TestUtils; +import lang::rascal::lsp::refactor::Exception; + +test bool renameToReservedName() { + edits = getEdits("int foo = 8;", 0, "foo", "int"); + newNames = {name | e <- edits, changed(_, replaces) := e + , r <- replaces, replace(_, name) := r}; + + return newNames == {"\\int"}; +} + +@expected{illegalRename} +test bool renameToUsedReservedName() = testRename(" + 'int \\int = 0; + 'int foo = 8; +", newName = "int"); + +@expected{illegalRename} +test bool newNameIsNonAlphaNumeric() = testRename("int foo = 8;", newName = "b@r"); + +@expected{illegalRename} +test bool newNameIsNumber() = testRename("int foo = 8;", newName = "8"); + +@expected{illegalRename} +test bool newNameHasNumericPrefix() = testRename("int foo = 8;", newName = "8abc"); + +@expected{illegalRename} +test bool newNameIsEscapedInvalid() = testRename("int foo = 8;", newName = "\\8int"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc new file mode 100644 index 000000000..f787a4719 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -0,0 +1,166 @@ +module lang::rascal::tests::rename::Variables + +import lang::rascal::tests::rename::TestUtils; +import lang::rascal::lsp::refactor::Exception; + + +//// Local + +test bool freshName() = {0} == testRenameOccurrences(" + 'int foo = 8; + 'int qux = 10; +"); + +test bool shadowVariableInInnerScope() = {0} == testRenameOccurrences(" + 'int foo = 8; + '{ + ' int bar = 9; + '} +"); + +test bool parameterShadowsVariable() = {0} == testRenameOccurrences(" + 'int foo = 8; + 'int f(int bar) { + ' return bar; + '} +"); + +@expected{illegalRename} +test bool implicitVariableDeclarationInSameScopeBecomesUse() = testRename(" + 'int foo = 8; + 'bar = 9; +"); + +@expected{illegalRename} +test bool implicitVariableDeclarationInInnerScopeBecomesUse() = testRename(" + 'int foo = 8; + '{ + ' bar = 9; + '} +"); + +@expected{illegalRename} +test bool doubleVariableDeclaration() = testRename(" + 'int foo = 8; + 'int bar = 9; +"); + +test bool adjacentScopes() = {0} == testRenameOccurrences(" + '{ + ' int foo = 8; + '} + '{ + ' int bar = 9; + '} +"); + +@expected{illegalRename} +test bool implicitPatterVariableInSameScopeBecomesUse() = testRename(" + 'int foo = 8; + 'bar := 9; +"); + +@expected{illegalRename} +test bool implicitNestedPatterVariableInSameScopeBecomesUse() = testRename(" + 'int foo = 8; + '\ := \<9, 99\>; +"); + +@expected{illegalRename} +test bool implicitPatterVariableInInnerScopeBecomesUse() = testRename(" + 'int foo = 8; + 'if (bar := 9) { + ' temp = 2 * bar; + '} +"); + +test bool explicitPatternVariableInInnerScope() = {0} == testRenameOccurrences(" + 'int foo = 8; + 'if (int bar := 9) { + ' bar = 2 * bar; + '} +"); + +test bool becomesPatternInInnerScope() = {0} == testRenameOccurrences(" + 'int foo = 8; + 'if (bar : int _ := 9) { + ' bar = 2 * bar; + '} +"); + +@expected{illegalRename} +test bool implicitPatternVariableBecomesInInnerScope() = testRename(" + 'int foo = 8; + 'if (bar : _ := 9) { + ' bar = 2 * foo; + '} +"); + +@expected{illegalRename} +test bool explicitPatternVariableBecomesInInnerScope() = testRename(" + 'int foo = 8; + 'if (bar : int _ := 9) { + ' bar = 2 * foo; + '} +"); + +@expected{illegalRename} +test bool shadowDeclaration() = testRename(" + 'int foo = 8; + 'if (int bar := 9) { + ' foo = 2 * bar; + '} +"); + +// Although this is fine statically, it will cause runtime errors when `bar` is called +// > A value of type int is not something you can call like a function, a constructor or a closure. +@expected{illegalRename} +test bool doubleVariableAndFunctionDeclaration() = testRename(" + 'int foo = 8; + 'void bar() {} +"); + +// Although this is fine statically, it will cause runtime errors when `bar` is called +// > A value of type int is not something you can call like a function, a constructor or a closure. +@expected{illegalRename} +test bool doubleFunctionAndVariableDeclaration() = testRename(" + 'void bar() {} + 'foo = 8; +"); + +@expected{illegalRename} +test bool doubleFunctionAndNestedVariableDeclaration() = testRename(" + 'bool bar() = true; + 'void f() { + ' int foo = 0; + '} +"); + + +//// Global + +test bool globalVar() = {0, 3} == testRenameOccurrences(" + 'int f(int foo) = foo; + 'foo = 16; +", decls = " + 'int foo = 8; +"); + +test bool multiModuleVar() = testRenameOccurrences(( + "alg::Fib": <"int foo = 8; + ' + 'int fib(int n) { + ' if (n \< 2) { + ' return 1; + ' } + ' return fib(n - 1) + fib(n -2); + '}" + , {0}> + , "Main": <"import alg::Fib; + ' + 'int main() { + ' fib(alg::Fib::foo); + ' return 0; + '}" + , {0}> + ), <"Main", "foo", 0>, newName = "Bar"); diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/RenameTests.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/RenameTests.java index ec5211cb1..a96ea4ec8 100644 --- a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/RenameTests.java +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/RenameTests.java @@ -31,5 +31,5 @@ import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner; @RunWith(RascalJUnitTestRunner.class) -@RascalJUnitTestPrefix("lang::rascal::tests") +@RascalJUnitTestPrefix("lang::rascal::tests::rename") public class RenameTests {} From 098b303e705d338c0e056880e0a7273d01af4f41 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 3 Jul 2024 14:23:00 +0200 Subject: [PATCH 011/105] Simplify workspace info collection. --- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 8ff42aa9a..d995065db 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -16,34 +16,24 @@ data WorkspaceInfo ( set[loc] modules = {} ) = workspaceInfo(set[loc] folders, PathConfig pcfg); -private WorkspaceInfo loadModule(WorkspaceInfo ws, loc l) { - WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { - ws.useDef += tm.useDef; - ws.defines += tm.defines; - - return ws; - } - - moduleLoc = l.top; - - if (moduleLoc notin ws.modules) { - moduleName = getModuleName(moduleLoc, ws.pcfg); - - // Only check one module at a time, to limit the amount of memory used - ms = rascalTModelForLocs([moduleLoc], getRascalCoreCompilerConfig(ws.pcfg), dummy_compile1); - - ws = loadModel(ws, ms.tmodels[moduleName]); - ws.modules += { moduleLoc }; - } +private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { + ws.useDef += tm.useDef; + ws.defines += tm.defines; return ws; } WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { ws = workspaceInfo(folders, pcfg); - for (f <- folders, m <- find(f, "rsc")) { - ws = loadModule(ws, m); + + mods = [m | f <- folders, m <- find(f, "rsc")]; + ms = rascalTModelForLocs(mods, getRascalCoreCompilerConfig(pcfg), dummy_compile1); + + for (m <- ms.tmodels) { + ws = loadModel(ws, ms.tmodels[m]); + ws.modules += {ms.moduleLocs[m].top}; } + return ws; } From 58c81e90645ff0bd2414eeea4065c7d72c8d507b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 3 Jul 2024 14:23:33 +0200 Subject: [PATCH 012/105] Always use fresh environment for compiler/typechecker. --- .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 93f5d2952..2787d9732 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -50,6 +50,7 @@ alias TestModule = tuple[str body, set[int] expectedRenameOccs]; bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { str testName = "Test"; loc testDir = |memory://tests/rename/|; + remove(testDir); PathConfig pcfg = getMultiModuleConfig(testDir); @@ -112,8 +113,8 @@ private PathConfig getMultiModuleConfig(loc testDir) { bin=testDir + "bin", libs=[|lib://rascal|], srcs=[testDir + "rascal"], - resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generated-sources| + resources=testDir + "resources", + generatedSources=testDir + "generated-sources" ); } From e740621c4f3aee457ebef6ccbd35147dad8bffb5 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 3 Jul 2024 15:41:02 +0200 Subject: [PATCH 013/105] Use JUnit 4 only. --- rascal-lsp/pom.xml | 28 ++++++------------- .../lsp/util/LineColumnOffsetMapTests.java | 14 +++++----- .../swat/rascal/lsp/util/LookupTests.java | 7 +++-- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml index 0a6e325ff..6d0c4b0d9 100644 --- a/rascal-lsp/pom.xml +++ b/rascal-lsp/pom.xml @@ -33,7 +33,7 @@ 2.20.2-SNAPSHOT UTF-8 - 5.10.3 + 4.13.1 3.3.0 2.23.1 0.23.1 @@ -75,29 +75,10 @@ typepal 0.13.2 - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - test - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - junit junit - 4.13.1 - test - - - - org.junit.vintage - junit-vintage-engine ${junit.version} test @@ -169,6 +150,13 @@ org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} + + + org.apache.maven.surefire + surefire-junit47 + 3.3.0 + + org.rascalmpl diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LineColumnOffsetMapTests.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LineColumnOffsetMapTests.java index 1c507942b..a5f2bcfff 100644 --- a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LineColumnOffsetMapTests.java +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LineColumnOffsetMapTests.java @@ -28,19 +28,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.Test; import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap; import org.rascalmpl.vscode.lsp.util.locations.impl.ArrayLineOffsetMap; public class LineColumnOffsetMapTests { @Test - void noUnicodeChars() { + public void noUnicodeChars() { LineColumnOffsetMap map = ArrayLineOffsetMap.build("1234\n1234"); assertEquals(2, map.translateColumn(0, 2, false)); } @Test - void singleWideChar() { + public void singleWideChar() { LineColumnOffsetMap map = ArrayLineOffsetMap.build("12🎉45\n1234🎉"); assertEquals(3, map.translateColumn(0, 3, false)); assertEquals(4, map.translateColumn(0, 3, true)); @@ -49,7 +49,7 @@ void singleWideChar() { @Test - void doubleChars() { + public void doubleChars() { LineColumnOffsetMap map = ArrayLineOffsetMap.build("12🎉4🎉6\n1234"); assertEquals(6, map.translateColumn(0, 5, false)); assertEquals(7, map.translateColumn(0, 5, true)); @@ -57,13 +57,13 @@ void doubleChars() { } @Test - void noUnicodeCharsInverse() { + public void noUnicodeCharsInverse() { LineColumnOffsetMap map = ArrayLineOffsetMap.build("1234\n1234"); assertEquals(2, map.translateInverseColumn(0, 2, false)); } @Test - void singleWideCharInverse() { + public void singleWideCharInverse() { LineColumnOffsetMap map = ArrayLineOffsetMap.build("12🎉45\n1234🎉"); assertEquals(3, map.translateInverseColumn(0, 3, false)); assertEquals(3, map.translateInverseColumn(0, 4, false)); @@ -72,7 +72,7 @@ void singleWideCharInverse() { @Test - void doubleCharsInverse() { + public void doubleCharsInverse() { LineColumnOffsetMap map = ArrayLineOffsetMap.build("12🎉4🎉6\n1234"); assertEquals(5, map.translateInverseColumn(0, 6, false)); assertEquals(5, map.translateInverseColumn(0, 7, true)); diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LookupTests.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LookupTests.java index ffd6c0a0a..4ed1cc998 100644 --- a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LookupTests.java +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LookupTests.java @@ -26,13 +26,14 @@ */ package engineering.swat.rascal.lsp.util; -import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.Assert.assertSame; + import java.util.HashMap; import java.util.Map; import java.util.Random; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; -import org.junit.jupiter.api.Test; +import org.junit.Test; import org.rascalmpl.vscode.lsp.util.locations.impl.TreeMapLookup; public class LookupTests { @@ -157,7 +158,7 @@ public void randomRanges() { ranges.forEach(target::put); for (var e: ranges.entrySet()) { var found = target.lookup(e.getKey()); - assertSame(e.getValue(), found, "Entry " + e + "should be found"); + assertSame("Entry " + e + "should be found", e.getValue(), found); } } From ef290e1dd3645b6f2b0e5d69476da1d649aa055c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 4 Jul 2024 18:11:39 +0200 Subject: [PATCH 014/105] Implement function overload renaming. --- .../lang/rascal/lsp/refactor/Rename.rsc | 26 +++++--- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 30 ++++++++- .../lang/rascal/tests/rename/Functions.rsc | 64 +++++++++++++++++++ 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 30af72cfd..97cd99e4b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -188,7 +188,22 @@ private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTex private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[Define] defs, set[loc] uses, str name) = computeTextEdits(ws, parseModuleWithSpaces(moduleLoc), defs, uses, name); -private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, set[Define] defs, set[loc] uses, str name) { +private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { + set[str] names = {definitions[l].id | l <- defs}; + if (size(names) > 1) { + return false; + } + + map[loc, Define] potentialOverloadDefinitions = (l: d | l <- definitions, d := definitions[l], d.id in names); + return rascalMayOverload(defs, potentialOverloadDefinitions); +} + +private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, str name) { + loc useDefAtCursor = findSmallestContaining(ws.defines.defined + ws.useDef<0>, cursor); + + set[Define] defs = getOverloadedDefines(ws, useDefAtCursor, rascalMayOverloadSameName); + set[loc] uses = ({} | it + getUses(ws, def) | def <- defs.defined); + rel[loc file, Define defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; @@ -209,15 +224,8 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, se } list[DocumentEdit] renameRascalSymbol(Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { - loc cursorLoc = cursor.src; - WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, pcfg); - - loc useDefAtCursor = findSmallestContaining(ws.defines.defined + ws.useDef<0>, cursorLoc); - set[Define] defs = getDefines(ws, useDefAtCursor); - set[loc] uses = ({} | it + getUses(ws, def) | def <- defs.defined); - - return computeDocumentEdits(ws, cursorLoc, defs, uses, newName); + return computeDocumentEdits(ws, cursor.src, newName); } //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index d995065db..97b2186ca 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -10,15 +10,19 @@ import lang::rascalcore::check::Checker; import util::FileSystem; import util::Reflective; +alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines); + data WorkspaceInfo ( rel[loc use, loc def] useDef = {}, set[Define] defines = {}, - set[loc] modules = {} + set[loc] modules = {}, + map[loc, Define] definitions = () ) = workspaceInfo(set[loc] folders, PathConfig pcfg); private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { ws.useDef += tm.useDef; ws.defines += tm.defines; + ws.definitions += tm.definitions; return ws; } @@ -38,6 +42,26 @@ WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { } set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; + set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; -set[Define] getDefines(WorkspaceInfo ws, set[loc] defs) = {def | d <- defs, def:<_, _, _, _, d, _> <- ws.defines}; -set[Define] getDefines(WorkspaceInfo ws, loc useDef) = getDefines(ws, getDefs(ws, useDef) + useDef); + +set[Define] getOverloadedDefines(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { + set[loc] overloadedLocs = defs; + set[Define] overloadedDefs = {ws.definitions[l] | l <- defs}; + + // Pre-condition + assert mayOverloadF(overloadedLocs, ws.definitions): + "Initial defs are invalid overloads!"; + + for (loc d <- ws.definitions) { + if (mayOverloadF(defs + d, ws.definitions)) { + overloadedLocs += d; + overloadedDefs += ws.definitions[d]; + } + } + + return overloadedDefs; +} + +set[Define] getOverloadedDefines(WorkspaceInfo ws, loc useDef, MayOverloadFun mayOverloadF) = + getOverloadedDefines(ws, getDefs(ws, useDef) != {} ? getDefs(ws, useDef) : {useDef}, mayOverloadF); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index 929493e79..3134be349 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -81,3 +81,67 @@ test bool privateFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = ' return f; '} "); + +test bool backtrackOverloadFromUse() = {0, 1, 2} == testRenameOccurrences("x = foo(3);", decls = " + 'int foo(int x) = x when x \< 2; + 'default int foo(int x) = x; +", cursorAtOldNameOccurrence = -1); + +test bool backtrackOverloadFromDef() = {0, 1, 2} == testRenameOccurrences("x = foo(3);", decls = " + 'int foo(int x) = x when x \< 2; + 'default int foo(int x) = x; +"); + +test bool patternOverloadFromUse() = {0, 1, 2, 3} == testRenameOccurrences("x = size([1, 2]);", decls = " + 'int size(list[&T] _: []) = 0; + 'int size(list[&T] _: [_, *l]) = 1 + size(l); +", cursorAtOldNameOccurrence = -1, oldName = "size", newName = "sizeof"); + +test bool patternOverloadFromDef() = {0, 1, 2, 3} == testRenameOccurrences("x = size([1, 2]);", decls = " + 'int size(list[&T] _: []) = 0; + 'int size(list[&T] _: [_, *l]) = 1 + size(l); +", oldName = "size", newName = "sizeof"); + +test bool typeOverloadFromUse() = {0, 1, 2, 3, 4, 5, 6} == testRenameOccurrences("x = size([1, 2]);", decls = " + 'int size(list[&T] _: []) = 0; + 'int size(list[&T] _: [_, *l]) = 1 + size(l); + ' + 'int size(set[&T] _: {}) = 0; + 'int size(set[&T] _: {_, *s}) = 1 + size(s); +", cursorAtOldNameOccurrence = -1, oldName = "size", newName = "sizeof"); + +test bool typeOverloadFromDef() = {0, 1, 2, 3, 4, 5, 6} == testRenameOccurrences("x = size([1, 2]);", decls = " + 'int size(list[&T] _: []) = 0; + 'int size(list[&T] _: [_, *l]) = 1 + size(l); + ' + 'int size(set[&T] _: {}) = 0; + 'int size(set[&T] _: {_, *s}) = 1 + size(s); +", oldName = "size", newName = "sizeof"); + +test bool arityOverloadFromUse() = {0, 1, 2, 3, 4, 5} == testRenameOccurrences("x = concat(\"foo\", \"bar\");", decls = " + 'str concat(str s) = s; + 'str concat(str s1, str s2) = s1 + concat(s2); + 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); +", cursorAtOldNameOccurrence = -1, oldName = "concat", newName = "foo"); + +test bool arityOverloadFromDef() = {0, 1, 2, 3, 4, 5} == testRenameOccurrences("x = concat(\"foo\", \"bar\");", decls = " + 'str concat(str s) = s; + 'str concat(str s1, str s2) = s1 + concat(s2); + 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); +", oldName = "concat", newName = "foo"); + +test bool overloadClash() = {0} == testRenameOccurrences("", decls = " + 'int foo = 0; + 'int foo(int x) = x when x \< 2; +"); + +test bool crossModuleOverload() = testRenameOccurrences(( + "Str": <" + 'str concat(str s) = s; + 'str concat(str s1, str s2) = s1 + concat(s2); + ", {0, 1, 2}> + , "Main": <" + 'extend Str; + 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); + ", {0, 1}> +), <"Main", "concat", 0>, newName = "conc"); From 46454f3976b4a88ffc8c8d4fab7b66056e8f3253 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 5 Jul 2024 13:06:48 +0200 Subject: [PATCH 015/105] Fix type errors. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 7 +++---- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 97cd99e4b..2b5ced04f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -39,7 +39,6 @@ import Exception; import IO; import List; import Location; -import Node; import ParseTree; import Relation; import Set; @@ -72,7 +71,7 @@ set[IllegalRenameReason] checkLegalName(str name) { } private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = - { definitionsOutsideWorkspace(d) | d <- groupRangeByDomain({ | d <- defs, f := d.top, f notin ws.modules}) }; + { definitionsOutsideWorkspace(d) | set[loc] d <- groupRangeByDomain({ | loc d <- defs, f := d.top, f notin ws.modules}) }; private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Define] currentDefs, set[Define] newDefs) { // Is newName already resolvable from a scope where is currently declared? @@ -129,9 +128,9 @@ private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Mod } private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, str newName) { - set[Define] newNameDefs = {def | def:<_, newName, _, _, _, _> <- ws.defines}; + set[Define] newNameDefs = {def | Define def:<_, newName, _, _, _, _> <- ws.defines}; - return reasons = + return checkLegalName(newName) + checkDefinitionsOutsideWorkspace(ws, currentDefs.defined) + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 97b2186ca..f04f17fee 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -1,6 +1,5 @@ module lang::rascal::lsp::refactor::WorkspaceInfo -import IO; import Relation; import analysis::typepal::TModel; From abd5a6500cad81dfd1aa7b10ed8b6aa300625924 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 5 Jul 2024 13:07:07 +0200 Subject: [PATCH 016/105] Fix test path config. --- .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 2787d9732..b48126a8c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -122,8 +122,8 @@ private PathConfig getTestWorkspaceConfig() { return pathConfig( bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, libs=[|lib://rascal|], - srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main| - , |project://rascal-vscode-extension/test-workspace/test-lib/src/main|], + srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal| + , |project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal|], resources=|memory://tests/rename/resources|, generatedSources=|memory://tests/rename/generatedSources| ); From 713d8079f4eb247c6afcdc2f200c8f2677ee2310 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 8 Jul 2024 16:11:06 +0200 Subject: [PATCH 017/105] Adapt to new typechecker version. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 4 ++-- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 2b5ced04f..e20e40621 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -141,8 +141,8 @@ private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[M private str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; // TODO Move to stdlib? -private loc findSmallestContaining(set[loc] wrappers, loc l) = - (|unknown:///| | w < it && isContainedIn(l, w) ? w : it | w <- wrappers); +private loc findSmallestContaining(set[loc] wrappers, loc l, loc sentinel = |unknown:///|) = + (sentinel | (it == sentinel || w < it) && isContainedIn(l, w) ? w : it | w <- wrappers); // Find the smallest trees of defined non-terminal type with a source location in `useDefs` private set[loc] findNames(start[Module] m, set[loc] useDefs) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index f04f17fee..d5b2eb56d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -33,7 +33,8 @@ WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { ms = rascalTModelForLocs(mods, getRascalCoreCompilerConfig(pcfg), dummy_compile1); for (m <- ms.tmodels) { - ws = loadModel(ws, ms.tmodels[m]); + tm = convertTModel2PhysicalLocs(ms.tmodels[m]); + ws = loadModel(ws, tm); ws.modules += {ms.moduleLocs[m].top}; } From 4aa54fb86f8844db3b17c41db90c69b8cce1add2 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 8 Jul 2024 16:12:36 +0200 Subject: [PATCH 018/105] Align single/multi module tests, simplify, remove duplicate type check. --- .../lang/rascal/tests/rename/TestUtils.rsc | 83 ++++++------------- .../lang/rascal/tests/rename/ValidNames.rsc | 2 +- 2 files changed, 27 insertions(+), 58 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index b48126a8c..baa4a543b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -29,6 +29,7 @@ module lang::rascal::tests::rename::TestUtils import lang::rascal::lsp::refactor::Rename; // Module under test import IO; +import List; import Map; import Message; import Set; @@ -50,23 +51,19 @@ alias TestModule = tuple[str body, set[int] expectedRenameOccs]; bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { str testName = "Test"; loc testDir = |memory://tests/rename/|; + remove(testDir); - PathConfig pcfg = getMultiModuleConfig(testDir); + PathConfig pcfg = getTestPathConfig(testName=testName, testDir=testDir); map[loc file, set[int] expectedRenameOccs] modulesByLocation = (l: m.expectedRenameOccs | name <- modules, m := modules[name], l := storeTestModule(testDir, name, m.body)); - - res = testRenameOccurrences(modulesByLocation, cursor, newName=newName, pcfg=pcfg); - remove(testDir); - return res; + return testRenameOccurrences(modulesByLocation, cursor, newName=newName, pcfg=pcfg); } bool testRenameOccurrences(map[loc file, set[int] expectedRenameOccs] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar", PathConfig pcfg = getTestWorkspaceConfig()) { - checkNoErrors(check(toList(domain(modules)), rascalCompilerConfig(pcfg))); - Tree cursorT = findCursor([l | l <- modules, getModuleName(l, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); - edits = renameRascalSymbol(cursorT, {d| d <- pcfg.srcs}, pcfg, newName); + edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), pcfg, newName); editsPerModule = (name: locsToOccs(m, cursor.id, locs) | changed(f, textedits) <- edits , start[Module] m := parseModuleWithSpaces(f) @@ -77,22 +74,15 @@ bool testRenameOccurrences(map[loc file, set[int] expectedRenameOccs] modules, t return editsPerModule == expectedEditsPerModule; } -set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); +set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); occs = extractRenameOccurrences(moduleFileName, edits, oldName); - remove(moduleFileName); return occs; } -list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "") { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); - remove(l); - return edits; -} - // Test renames that are expected to throw an exception -bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "") { - edits = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls=decls); +bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { + edits = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); print("UNEXPECTED EDITS: "); iprintln(edits); @@ -100,15 +90,7 @@ bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = " return false; } - -private PathConfig testPathConfig = pathConfig( - bin=|memory://tests/rename/bin|, - libs=[|lib://rascal|], - srcs=[|memory://tests/rename/src|], - resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generated-sources|); - -private PathConfig getMultiModuleConfig(loc testDir) { +private PathConfig getTestPathConfig(str testName = "Test", loc testDir = |memory://tests/rename/|) { return pathConfig( bin=testDir + "bin", libs=[|lib://rascal|], @@ -129,28 +111,20 @@ private PathConfig getTestWorkspaceConfig() { ); } -// Test renaming given the location of a module and rename parameters -list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { - void checkNoErrors(list[ModuleMessages] msgs) { - if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) - throw errors; - } - +list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig()) { loc f = resolveLocation(singleModule); + m = parseModuleWithSpaces(f); - checkNoErrors(checkAll(f, getRascalCoreCompilerConfig(pcfg))); - - return getEdits(parseModuleWithSpaces(f), cursorAtOldNameOccurrence, oldName, newName, pcfg=pcfg); + Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; + return renameRascalSymbol(cursor, toSet(pcfg.srcs), pcfg, newName); } -// Test renaming given a module Tree and rename parameters -list[DocumentEdit] getEdits(start[Module] m, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = testPathConfig) { - Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(cursor, {src | src <- pcfg.srcs}, pcfg, newName); +list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports) { + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); + return edits; } -private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls = "", str imports = "") { - str moduleName = "TestModule"; +private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule", PathConfig pcfg = getTestPathConfig(testName=moduleName)) { str moduleStr = "module ' @@ -158,21 +132,18 @@ private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int curso ' '}"; + for (src <- pcfg.srcs) { + remove(src.parent); // strip off `/rascal` + } + // Write the file to disk (and clean up later) to easily emulate typical editor behaviour - loc moduleFileName = |memory://tests/rename/src/.rsc|; + loc moduleFileName = pcfg.srcs[0] + ".rsc"; writeFile(moduleFileName, moduleStr); - list[DocumentEdit] edits = []; - try { - edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName); - } catch e: { - remove(moduleFileName); - throw e; - } + edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName, pcfg=pcfg); return ; } - private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { start[Module] m = parseModuleWithSpaces(moduleFileName); list[loc] oldNameOccurrences = []; @@ -224,7 +195,5 @@ private set[Tree] occsToTrees(start[Module] m, str name, set[int] occs) = {n | i private set[loc] occsToLocs(start[Module] m, str name, set[int] occs) = {t.src | t <- occsToTrees(m, name, occs)}; private set[int] locsToOccs(start[Module] m, str name, set[loc] occs) = {indexOf(names, occ) | names := [n.src | /Name n := m.top, "" == name], occ <- occs}; -private void checkNoErrors(list[ModuleMessages] msgs) { - if (errors := {p | p:program(_, pmsgs) <- msgs, m <- pmsgs, m is error}, errors != {}) - throw errors; -} +// Workaround to be able to pattern match on the emulated `src` field +data Tree (loc src = |unknown:///|(0,0,<0,0>,<0,0>)); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc index 72b351c38..50f4f44cd 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc @@ -4,7 +4,7 @@ import lang::rascal::tests::rename::TestUtils; import lang::rascal::lsp::refactor::Exception; test bool renameToReservedName() { - edits = getEdits("int foo = 8;", 0, "foo", "int"); + edits = getEdits("int foo = 8;", 0, "foo", "int", "", ""); newNames = {name | e <- edits, changed(_, replaces) := e , r <- replaces, replace(_, name) := r}; From 6b1b44e6adcfbd542fa909f6bd43d0766acdef5b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 8 Jul 2024 16:14:14 +0200 Subject: [PATCH 019/105] Distinguish role of id at cursor. --- .../lang/rascal/lsp/refactor/Exception.rsc | 4 +++- .../rascal/lang/rascal/lsp/refactor/Rename.rsc | 18 ++++++++++++++---- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 12 ++++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index b88e730fa..079f46741 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -1,5 +1,7 @@ module lang::rascal::lsp::refactor::Exception +data Cursor; + alias Capture = tuple[loc def, loc use]; data IllegalRenameReason @@ -10,7 +12,7 @@ data IllegalRenameReason ; data RenameException - = illegalRename(loc location, set[IllegalRenameReason] reason) + = illegalRename(Cursor cursor, set[IllegalRenameReason] reason) | unsupportedRename(rel[loc location, str message] issues) | unexpectedFailure(str message) ; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index e20e40621..9b6af7f0e 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -197,10 +197,20 @@ private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitio return rascalMayOverload(defs, potentialOverloadDefinitions); } -private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, str name) { - loc useDefAtCursor = findSmallestContaining(ws.defines.defined + ws.useDef<0>, cursor); +private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, str name) { + loc cursorLoc = cursorT.src; + str cursorName = ""; - set[Define] defs = getOverloadedDefines(ws, useDefAtCursor, rascalMayOverloadSameName); + cursorNamedDefs = (ws.defines)[cursorName]; + smallestUse = findSmallestContaining(ws.useDef<0>, cursorLoc); + smallestDef = findSmallestContaining(cursorNamedDefs, cursorLoc); + + Cursor cursor = smallestUse < smallestDef ? use(smallestUse) : def(smallestDef); + + if (field(l) := cursor) throw unsupportedRename({}); + if (cursor.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); + + set[Define] defs = getOverloadedDefines(ws, cursor, rascalMayOverloadSameName); set[loc] uses = ({} | it + getUses(ws, def) | def <- defs.defined); rel[loc file, Define defines] defsPerFile = { | d <- defs}; @@ -224,7 +234,7 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, loc cursor, st list[DocumentEdit] renameRascalSymbol(Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, pcfg); - return computeDocumentEdits(ws, cursor.src, newName); + return computeDocumentEdits(ws, cursor, newName); } //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index d5b2eb56d..bd3e20252 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -9,6 +9,11 @@ import lang::rascalcore::check::Checker; import util::FileSystem; import util::Reflective; +data Cursor = use(loc l) + | def(loc l) + | field(loc l) + ; + alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines); data WorkspaceInfo ( @@ -63,5 +68,8 @@ set[Define] getOverloadedDefines(WorkspaceInfo ws, set[loc] defs, MayOverloadFun return overloadedDefs; } -set[Define] getOverloadedDefines(WorkspaceInfo ws, loc useDef, MayOverloadFun mayOverloadF) = - getOverloadedDefines(ws, getDefs(ws, useDef) != {} ? getDefs(ws, useDef) : {useDef}, mayOverloadF); +set[Define] getOverloadedDefines(WorkspaceInfo ws, use(cursor), MayOverloadFun mayOverloadF) = + getOverloadedDefines(ws, getDefs(ws, cursor), mayOverloadF); + +set[Define] getOverloadedDefines(WorkspaceInfo ws, def(cursor), MayOverloadFun mayOverloadF) = + getOverloadedDefines(ws, {cursor}, mayOverloadF); From 3c28e25c954730eceb5d8d44cb963fd2710e11f6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 9 Jul 2024 11:37:09 +0200 Subject: [PATCH 020/105] Use generic locations instead of defines to prepare for fact-based lookup. --- .../lang/rascal/lsp/refactor/Rename.rsc | 26 +++++++++---------- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 19 ++++++++------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 9b6af7f0e..0cd9805a5 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -75,11 +75,10 @@ private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Define] currentDefs, set[Define] newDefs) { // Is newName already resolvable from a scope where is currently declared? - map[loc, Define] defMap = (def.defined: def | def <- ws.defines); rel[loc old, loc new] doubleDeclarations = { | Define cD <- currentDefs , Define nD <- newDefs , isContainedIn(cD.defined, nD.scope) - , !rascalMayOverload({cD.defined, nD.defined}, defMap) + , !rascalMayOverload({cD.defined, nD.defined}, ws.definitions) }; return {doubleDeclaration(old, doubleDeclarations[old]) | old <- doubleDeclarations.old}; @@ -93,7 +92,6 @@ private set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, s private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, set[Define] newDefs) { set[Define] newNameImplicitDefs = findImplicitDefinitions(ws, m, newDefs); - set[Define] newNameExplicitDefs = newDefs - newNameImplicitDefs; // Will this rename turn an implicit declaration of `newName` into a use of a current declaration? set[Capture] implicitDeclBecomesUseOfCurrentDecl = @@ -127,14 +125,15 @@ private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Mod return allCaptures == {} ? {} : {captureChange(allCaptures)}; } -private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, str newName) { +private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, str newName) { + set[Define] currentDefines = {ws.definitions[d] | d <- currentDefs}; set[Define] newNameDefs = {def | Define def:<_, newName, _, _, _, _> <- ws.defines}; return checkLegalName(newName) - + checkDefinitionsOutsideWorkspace(ws, currentDefs.defined) - + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs) - + checkCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs) + + checkDefinitionsOutsideWorkspace(ws, currentDefs) + + checkCausesDoubleDeclarations(ws, currentDefines, newNameDefs) + + checkCausesCaptures(ws, m, currentDefines, currentUses, newNameDefs) ; } @@ -174,17 +173,16 @@ Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is default Maybe[loc] locationOfName(Tree t) = nothing(); - -private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[Define] defs, set[loc] uses, str name) { +private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[loc] defs, set[loc] uses, str name) { if (reasons := collectIllegalRenames(ws, m, defs, uses, name), reasons != {}) { return ; } replaceName = escapeName(name); - return <{}, [replace(l, replaceName) | l <- findNames(m, defs.defined + uses)]>; + return <{}, [replace(l, replaceName) | l <- findNames(m, defs + uses)]>; } -private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[Define] defs, set[loc] uses, str name) = +private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[loc] defs, set[loc] uses, str name) = computeTextEdits(ws, parseModuleWithSpaces(moduleLoc), defs, uses, name); private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { @@ -210,10 +208,10 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, if (field(l) := cursor) throw unsupportedRename({}); if (cursor.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); - set[Define] defs = getOverloadedDefines(ws, cursor, rascalMayOverloadSameName); - set[loc] uses = ({} | it + getUses(ws, def) | def <- defs.defined); + set[loc] defs = getRelatedDefs(ws, cursor, rascalMayOverloadSameName); + set[loc] uses = getUses(ws, defs); - rel[loc file, Define defines] defsPerFile = { | d <- defs}; + rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; files = defsPerFile.file + usesPerFile.file; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index bd3e20252..a353f82d6 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -46,13 +46,17 @@ WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { return ws; } +@memo set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; +@memo +set[loc] getUses(WorkspaceInfo ws, set[loc] defs) = invert(ws.useDef)[defs]; + +@memo set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; -set[Define] getOverloadedDefines(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { +set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { set[loc] overloadedLocs = defs; - set[Define] overloadedDefs = {ws.definitions[l] | l <- defs}; // Pre-condition assert mayOverloadF(overloadedLocs, ws.definitions): @@ -61,15 +65,14 @@ set[Define] getOverloadedDefines(WorkspaceInfo ws, set[loc] defs, MayOverloadFun for (loc d <- ws.definitions) { if (mayOverloadF(defs + d, ws.definitions)) { overloadedLocs += d; - overloadedDefs += ws.definitions[d]; } } - return overloadedDefs; + return overloadedLocs; } -set[Define] getOverloadedDefines(WorkspaceInfo ws, use(cursor), MayOverloadFun mayOverloadF) = - getOverloadedDefines(ws, getDefs(ws, cursor), mayOverloadF); +set[loc] getRelatedDefs(WorkspaceInfo ws, use(cursor), MayOverloadFun mayOverloadF) = + getOverloadedDefs(ws, getDefs(ws, cursor), mayOverloadF); -set[Define] getOverloadedDefines(WorkspaceInfo ws, def(cursor), MayOverloadFun mayOverloadF) = - getOverloadedDefines(ws, {cursor}, mayOverloadF); +set[loc] getRelatedDefs(WorkspaceInfo ws, def(cursor), MayOverloadFun mayOverloadF) = + getOverloadedDefs(ws, {cursor}, mayOverloadF); From 070607284da01381488b91ce8d390097d1f4e62d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 11 Jul 2024 15:43:41 +0200 Subject: [PATCH 021/105] Ignore new source and test locations. --- rascal-lsp/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml index 6d0c4b0d9..82909493a 100644 --- a/rascal-lsp/pom.xml +++ b/rascal-lsp/pom.xml @@ -169,8 +169,8 @@ ${project.basedir}/src/main/rascal - ${project.basedir}/src/main/rascal/lang/rascal/lsp/Rename.rsc - ${project.basedir}/src/main/rascal/lang/rascal/tests/Rename.rsc + ${project.basedir}/src/main/rascal/lang/rascal/lsp/Refactor + ${project.basedir}/src/main/rascal/lang/rascal/tests/rename |lib://rascal-lsp| false From d02f5ca83504a2a9181ff4296a72d6101b52ae83 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 11 Jul 2024 15:44:30 +0200 Subject: [PATCH 022/105] Verify type-correctness before attempting rename. --- .../main/rascal/lang/rascal/lsp/refactor/Exception.rsc | 3 +++ .../rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index 079f46741..4862ff37c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -1,5 +1,7 @@ module lang::rascal::lsp::refactor::Exception +import Message; + data Cursor; alias Capture = tuple[loc def, loc use]; @@ -14,5 +16,6 @@ data IllegalRenameReason data RenameException = illegalRename(Cursor cursor, set[IllegalRenameReason] reason) | unsupportedRename(rel[loc location, str message] issues) + | unsupportedRename(list[Message] msgs) | unexpectedFailure(str message) ; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index a353f82d6..c316c52f5 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -27,15 +27,23 @@ private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { ws.useDef += tm.useDef; ws.defines += tm.defines; ws.definitions += tm.definitions; + ws.facts += tm.facts; return ws; } +private void checkNoErrors(ModuleStatus ms) { + errors = [msg | m <- ms.messages, msg <- ms.messages[m], msg is error]; + if (errors != []) + throw unsupportedRename(errors); +} + WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { ws = workspaceInfo(folders, pcfg); mods = [m | f <- folders, m <- find(f, "rsc")]; ms = rascalTModelForLocs(mods, getRascalCoreCompilerConfig(pcfg), dummy_compile1); + checkNoErrors(ms); for (m <- ms.tmodels) { tm = convertTModel2PhysicalLocs(ms.tmodels[m]); From 9bd0a446230e871415716a137c641cabe43a083f Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 11 Jul 2024 15:48:18 +0200 Subject: [PATCH 023/105] Implement fact-based support for type parameters. - Use location instead of `Define` as much as possible. - Load facts from TModels to calculate type var name locations. - Store kind of id under cursor in cursor. --- .../lang/rascal/lsp/refactor/Rename.rsc | 95 ++++++++++++------- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 21 ++++ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 67 +++++++++++-- .../lang/rascal/tests/rename/Functions.rsc | 33 +++++++ .../rascal/lang/rascal/tests/rename/Types.rsc | 8 +- 5 files changed, 181 insertions(+), 43 deletions(-) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 0cd9805a5..c05e31316 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -52,6 +52,7 @@ import lang::rascalcore::check::RascalConfig; import analysis::typepal::TypePal; import lang::rascal::lsp::refactor::Exception; +import lang::rascal::lsp::refactor::Util; import lang::rascal::lsp::refactor::WorkspaceInfo; import analysis::diff::edits::TextEdits; @@ -73,12 +74,12 @@ set[IllegalRenameReason] checkLegalName(str name) { private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = { definitionsOutsideWorkspace(d) | set[loc] d <- groupRangeByDomain({ | loc d <- defs, f := d.top, f notin ws.modules}) }; -private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[Define] currentDefs, set[Define] newDefs) { +private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[loc] currentDefs, set[Define] newDefs) { // Is newName already resolvable from a scope where is currently declared? - rel[loc old, loc new] doubleDeclarations = { | Define cD <- currentDefs - , Define nD <- newDefs - , isContainedIn(cD.defined, nD.scope) - , !rascalMayOverload({cD.defined, nD.defined}, ws.definitions) + rel[loc old, loc new] doubleDeclarations = { | loc cD <- currentDefs + , Define nD <- newDefs + , isContainedIn(cD, nD.scope) + , !rascalMayOverload({cD, nD.defined}, ws.definitions) }; return {doubleDeclaration(old, doubleDeclarations[old]) | old <- doubleDeclarations.old}; @@ -90,14 +91,14 @@ private set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, s || (def.idRole is patternVariableId && def.defined in maybeImplicitDefs)}; } -private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[Define] currentDefs, set[loc] currentUses, set[Define] newDefs) { +private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, set[Define] newDefs) { set[Define] newNameImplicitDefs = findImplicitDefinitions(ws, m, newDefs); // Will this rename turn an implicit declaration of `newName` into a use of a current declaration? set[Capture] implicitDeclBecomesUseOfCurrentDecl = { | Define nD <- newNameImplicitDefs - , <- currentDefs - , isContainedIn(nD.defined, cS) + , loc cD <- currentDefs + , isContainedIn(nD.defined, ws.definitions[cD].scope) }; // Will this rename hide a used definition of `oldName` behind an existing definition of `newName` (shadowing)? @@ -110,11 +111,12 @@ private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Mod // Will this rename hide a used definition of `newName` behind a definition of `oldName` (shadowing)? set[Capture] newUseShadowedByRename = - { | Define nD <- newDefs - , nU <- invert(ws.useDef)[newDefs.defined] - , Define cD <- currentDefs - , isContainedIn(cD.scope, nD.scope) - , isContainedIn(nU, cD.scope) + { | Define nD <- newDefs + , nU <- invert(ws.useDef)[newDefs.defined] + , loc cD <- currentDefs + , loc cS := ws.definitions[cD].scope + , isContainedIn(cS, nD.scope) + , isContainedIn(nU, cS) }; allCaptures = @@ -126,23 +128,18 @@ private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Mod } private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, str newName) { - set[Define] currentDefines = {ws.definitions[d] | d <- currentDefs}; set[Define] newNameDefs = {def | Define def:<_, newName, _, _, _, _> <- ws.defines}; return checkLegalName(newName) + checkDefinitionsOutsideWorkspace(ws, currentDefs) - + checkCausesDoubleDeclarations(ws, currentDefines, newNameDefs) - + checkCausesCaptures(ws, m, currentDefines, currentUses, newNameDefs) + + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs) + + checkCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs) ; } private str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; -// TODO Move to stdlib? -private loc findSmallestContaining(set[loc] wrappers, loc l, loc sentinel = |unknown:///|) = - (sentinel | (it == sentinel || w < it) && isContainedIn(l, w) ? w : it | w <- wrappers); - // Find the smallest trees of defined non-terminal type with a source location in `useDefs` private set[loc] findNames(start[Module] m, set[loc] useDefs) { set[loc] names = {}; @@ -170,7 +167,7 @@ Maybe[loc] locationOfName(Declaration d) = just(d.name.src) when d is annotation Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is \alias || d is dataAbstract || d is \data; - +Maybe[loc] locationOfName(TypeVar tv) = just(tv.name.src); default Maybe[loc] locationOfName(Tree t) = nothing(); private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[loc] defs, set[loc] uses, str name) { @@ -186,10 +183,8 @@ private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTex computeTextEdits(ws, parseModuleWithSpaces(moduleLoc), defs, uses, name); private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { - set[str] names = {definitions[l].id | l <- defs}; - if (size(names) > 1) { - return false; - } + set[str] names = {definitions[l].id | l <- defs, definitions[l]?}; + if (size(names) > 1) return false; map[loc, Define] potentialOverloadDefinitions = (l: d | l <- definitions, d := definitions[l], d.id in names); return rascalMayOverload(defs, potentialOverloadDefinitions); @@ -199,17 +194,51 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, loc cursorLoc = cursorT.src; str cursorName = ""; + println("Cursor is at id \'\' at "); + cursorNamedDefs = (ws.defines)[cursorName]; - smallestUse = findSmallestContaining(ws.useDef<0>, cursorLoc); - smallestDef = findSmallestContaining(cursorNamedDefs, cursorLoc); - Cursor cursor = smallestUse < smallestDef ? use(smallestUse) : def(smallestDef); + rel[loc l, CursorKind kind] locsContainingCursor = { + + | <- { + , cursorLoc), use()> + , + , + } + }; + + if (size(locsContainingCursor) == 0) { + throw unsupportedRename({}); + } + + loc c = min(locsContainingCursor.l); + Cursor cur = cursor(use(), |unknown:///|, ""); + switch (locsContainingCursor[c]) { + case {def(), *_}: { + // Cursor is at a definition + cur = cursor(def(), c, cursorName); + } + case {use(), *_}: { + if (size(getDefs(ws, c) & ws.defines.defined) > 0) { + // The cursor is at a use with corresponding definitions. + cur = cursor(use(), c, cursorName); + } else if (ws.facts[c]? && aparameter(cursorName, _) := ws.facts[c]) { + // The cursor is at a type parameter + cur = cursor(typeParam(), c, cursorName); + } else { + fail; + } + } + case {k}: { + cur = cursor(k, c, cursorName); + } + default: + throw unsupportedRename({">}); + } - if (field(l) := cursor) throw unsupportedRename({}); - if (cursor.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); + if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); - set[loc] defs = getRelatedDefs(ws, cursor, rascalMayOverloadSameName); - set[loc] uses = getUses(ws, defs); + = getDefsUses(ws, cur, rascalMayOverloadSameName); rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; @@ -219,7 +248,7 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, (file: | file <- files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], name)); if (reasons := union({moduleResults[file].reasons | file <- moduleResults}), reasons != {}) { - throw illegalRename(cursor, reasons); + throw illegalRename(cur, reasons); } list[DocumentEdit] changes = [changed(file, moduleResults[file].edits) | file <- moduleResults]; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc new file mode 100644 index 000000000..b47c870a6 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -0,0 +1,21 @@ +module lang::rascal::lsp::refactor::Util + +import List; +import Location; + +import util::Maybe; + +Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) = + (nothing() | (it == nothing() || (just(itt) := it && w < itt)) && isContainedIn(l, w) ? just(w) : it | w <- wrappers); + +loc min(list[loc] locs) = + (getFirstFrom(locs) | l < l ? l : it | l <- locs); + +loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { + return |://|( + l.offset + removePrefix - removeSuffix, + l.length - removePrefix - removeSuffix, + , + + ); +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index c316c52f5..7dc5c2fc3 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -9,10 +9,21 @@ import lang::rascalcore::check::Checker; import util::FileSystem; import util::Reflective; -data Cursor = use(loc l) - | def(loc l) - | field(loc l) - ; +import lang::rascal::lsp::refactor::Exception; +import lang::rascal::lsp::refactor::Util; + +import List; +import Location; +import Message; +import Set; +import String; + + +data CursorKind = use() + | def() + | typeParam(); + +data Cursor = cursor(CursorKind kind, loc l, str name); alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines); @@ -20,7 +31,8 @@ data WorkspaceInfo ( rel[loc use, loc def] useDef = {}, set[Define] defines = {}, set[loc] modules = {}, - map[loc, Define] definitions = () + map[loc, Define] definitions = (), + map[loc, AType] facts = () ) = workspaceInfo(set[loc] folders, PathConfig pcfg); private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { @@ -79,8 +91,45 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv return overloadedLocs; } -set[loc] getRelatedDefs(WorkspaceInfo ws, use(cursor), MayOverloadFun mayOverloadF) = - getOverloadedDefs(ws, getDefs(ws, cursor), mayOverloadF); +tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(use(), l, _), MayOverloadFun mayOverloadF) { + defs = getOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); + uses = getUses(ws, defs); + return ; +} + +tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(def(), l, _), MayOverloadFun mayOverloadF) { + defs = getOverloadedDefs(ws, {l}, mayOverloadF); + uses = getUses(ws, defs); + return ; +} + +tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _) { + AType at = ws.facts[cursorLoc]; + if(Define d: <_, _, _, _, _, defType(afunc(_, /at, _))> <- ws.defines, isContainedIn(cursorLoc, d.defined)) { + // From here on, we can assume that all locations are in the same file, because we are dealing with type parameters and filtered on `isContainedIn` + facts = { | l <- ws.facts + , at := ws.facts[l] + , isContainedIn(l, d.defined)}; -set[loc] getRelatedDefs(WorkspaceInfo ws, def(cursor), MayOverloadFun mayOverloadF) = - getOverloadedDefs(ws, {cursor}, mayOverloadF); + formals = {l | <- facts, f.alabel != ""}; + + // Given the location/offset of `&T`, find the location/offset of `T` + offsets = sort({l.offset | l <- facts<0>}); + nextOffsets = toMapUnique(zip2(prefix(offsets), tail(offsets))); + + loc sentinel = |unknown:///|(0, 0, <0, 0>, <0, 0>); + defs = {(sentinel | (it == sentinel || f.length < it.length) && f.offset == nextOffset ? f : it | f <- facts<0>) | formal <- formals, nextOffsets[formal.offset]?, nextOffset := nextOffsets[formal.offset]}; + + useDefs = {trim(l, removePrefix=prefixLength) + | l <- facts<0> + , !ws.definitions[l]? // If there is a definition at this location, this is a formal argument name + , !any(ud <- ws.useDef[l], ws.definitions[ud]?) // If there is a definition for the use at this location, this is a use of a formal argument + , !any(flInner <- facts<0>, isStrictlyContainedIn(flInner, l)) // Filter out any facts that contain other facts + , prefixLength := l.length - size(cursorName) + }; + + return ; + } + + throw unsupportedRename({\'">}); +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index 3134be349..0c2585edd 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -145,3 +145,36 @@ test bool crossModuleOverload() = testRenameOccurrences(( 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); ", {0, 1}> ), <"Main", "concat", 0>, newName = "conc"); + +test bool simpleTypeParams() = {0, 1} == testRenameOccurrences(" + '&T foo(&T l) = l; +", oldName = "T", newName = "U"); + +test bool typeParamsFromReturn() = {0, 1, 2} == testRenameOccurrences(" + '&T foo(&T l) { + ' &T m = l; + ' return m; + '} +", oldName = "T", newName = "U"); + +test bool keywordTypeParamFromReturn() = {0, 1, 2} == testRenameOccurrences(" + '&T foo(&T \<: int l, &T \<: int kw = 1) = l + kw; +", oldName = "T", newName = "U"); + +test bool typeParamsFromFormal() = {0, 1, 2} == testRenameOccurrences(" + '&T foo(&T l) { + ' &T m = l; + ' return m; + '} +", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); + +test bool typeParamsClash() = {0, 1} == testRenameOccurrences(" + '&T foo(&T l, &U m) = l; +", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); + +test bool typeParamsListReturn() = {0, 1, 2} == testRenameOccurrences(" + 'list[&T] foo(&T l) { + ' list[&T] m = [l, l]; + ' return m; + '} +", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index 8f158c9a5..2186d6608 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -2,12 +2,18 @@ module lang::rascal::tests::rename::Types import lang::rascal::tests::rename::TestUtils; -test bool globalAlias() = {0, 1, 2, 3} == testRenameOccurrences(" +test bool globalAliasFromDef() = {0, 1, 2, 3} == testRenameOccurrences(" 'Foo f(Foo x) = x; 'Foo foo = 8; 'y = f(foo); ", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar"); +test bool globalAliasFromUse() = {0, 1, 2, 3} == testRenameOccurrences(" + 'Foo f(Foo x) = x; + 'Foo foo = 8; + 'y = f(foo); +", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar", cursorAtOldNameOccurrence = 1); + test bool multiModuleAlias() = testRenameOccurrences(( "alg::Fib": <"alias Foo = int; 'Foo fib(int n) { From b52d2f9a24457189df3888f59a6fcd8564074616 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 16 Jul 2024 12:05:04 +0200 Subject: [PATCH 024/105] Add missing test cases for already implemented rename functionality. --- .../src/main/rascal/lang/rascal/tests/rename/Functions.rsc | 7 +++++++ .../src/main/rascal/lang/rascal/tests/rename/Variables.rsc | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index 0c2585edd..d526fc99d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -178,3 +178,10 @@ test bool typeParamsListReturn() = {0, 1, 2} == testRenameOccurrences(" ' return m; '} ", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); + +test bool localOverloadedFunction() = {0, 1, 2, 3} == testRenameOccurrences(" + 'bool foo(g()) = true; + 'bool foo(h()) = foo(g()); + ' + 'foo(h()); +", decls = "data D = g() | h();"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc index f787a4719..657cf2788 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -136,6 +136,13 @@ test bool doubleFunctionAndNestedVariableDeclaration() = testRename(" '} "); +test bool tupleVariable() = {0} == testRenameOccurrences("\ = \<0, 1\>;"); + +test bool tuplePatternVariable() = {0, 1} == testRenameOccurrences(" + 'if (\ := \<0, 1\>) + ' qux = foo; +"); + //// Global From 37819642a3c304bc96636e8d99141ba4c11ccb92 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 16 Jul 2024 12:20:37 +0200 Subject: [PATCH 025/105] Add license headers to new files. --- .../lang/rascal/lsp/refactor/Exception.rsc | 26 +++++++++++++++++++ .../rascal/lang/rascal/lsp/refactor/Util.rsc | 26 +++++++++++++++++++ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 26 +++++++++++++++++++ .../lang/rascal/tests/rename/Fields.rsc | 26 +++++++++++++++++++ .../rascal/tests/rename/FormalParameters.rsc | 26 +++++++++++++++++++ .../lang/rascal/tests/rename/Functions.rsc | 26 +++++++++++++++++++ .../lang/rascal/tests/rename/Performance.rsc | 26 +++++++++++++++++++ .../rascal/lang/rascal/tests/rename/Types.rsc | 26 +++++++++++++++++++ .../lang/rascal/tests/rename/ValidNames.rsc | 26 +++++++++++++++++++ .../lang/rascal/tests/rename/Variables.rsc | 26 +++++++++++++++++++ 10 files changed, 260 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index 4862ff37c..cb7226b23 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::lsp::refactor::Exception import Message; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index b47c870a6..c82f60000 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::lsp::refactor::Util import List; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 7dc5c2fc3..8edecba33 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::lsp::refactor::WorkspaceInfo import Relation; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 29cb0a34f..4853ee8d0 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Fields // import lang::rascal::tests::rename::TestUtils; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc index e77a42c12..478037d5f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::FormalParameters import lang::rascal::tests::rename::TestUtils; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index d526fc99d..fa0c50b66 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Functions import lang::rascal::tests::rename::TestUtils; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc index 8a26a532c..161fcc86d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Performance import lang::rascal::tests::rename::TestUtils; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index 2186d6608..d82024861 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Types import lang::rascal::tests::rename::TestUtils; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc index 50f4f44cd..349fd0cda 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::ValidNames import lang::rascal::tests::rename::TestUtils; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc index 657cf2788..81811b11c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -1,3 +1,29 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Variables import lang::rascal::tests::rename::TestUtils; From c480de335ccd7c237e5710c4fe80cf1e4bded55b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 16 Jul 2024 13:55:40 +0200 Subject: [PATCH 026/105] Use field assigment instead of interpolation. --- .../src/main/rascal/lang/rascal/lsp/refactor/Util.rsc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index c82f60000..599361545 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -38,10 +38,8 @@ loc min(list[loc] locs) = (getFirstFrom(locs) | l < l ? l : it | l <- locs); loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { - return |://|( - l.offset + removePrefix - removeSuffix, - l.length - removePrefix - removeSuffix, - , - - ); + return l[offset = l.offset + removePrefix - removeSuffix] + [length = l.length - removePrefix - removeSuffix] + [begin = ] + [end = ]; } From 7b472a22270c8d2d3a44b13f64cb555cec136be1 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 16 Jul 2024 17:44:28 +0200 Subject: [PATCH 027/105] Fix module location in evaluator. --- .../org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java index 0d1cdc2bb..233a6c2b7 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java @@ -82,7 +82,7 @@ public RascalLanguageServices(RascalTextDocumentService docService, BaseWorkspac var monitor = new RascalLSPMonitor(client, logger); outlineEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal outline", monitor, null, false, "lang::rascal::lsp::Outline"); - semanticEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal summary", monitor, null, true, "lang::rascalcore::check::Summary", "lang::rascal::lsp::Rename"); + semanticEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal summary", monitor, null, true, "lang::rascalcore::check::Summary", "lang::rascal::lsp::refactor::Rename"); compilerEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal compiler", monitor, null, true, "lang::rascalcore::check::Checker"); } From 07c5d5203973eccea9644f88f7581d4ba32cfeeb Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 16 Jul 2024 17:49:38 +0200 Subject: [PATCH 028/105] Include renames in test expectations. --- .../lang/rascal/tests/rename/Functions.rsc | 12 +-- .../lang/rascal/tests/rename/TestUtils.rsc | 82 +++++++++---------- .../rascal/lang/rascal/tests/rename/Types.rsc | 24 +++--- .../lang/rascal/tests/rename/Variables.rsc | 12 +-- 4 files changed, 63 insertions(+), 67 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index fa0c50b66..b59fbb757 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -161,16 +161,16 @@ test bool overloadClash() = {0} == testRenameOccurrences("", decls = " 'int foo(int x) = x when x \< 2; "); -test bool crossModuleOverload() = testRenameOccurrences(( - "Str": <" +test bool crossModuleOverload() = testRenameOccurrences({ + byText("Str", " 'str concat(str s) = s; 'str concat(str s1, str s2) = s1 + concat(s2); - ", {0, 1, 2}> - , "Main": <" + ", {0, 1, 2}) + , byText("Main", " 'extend Str; 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); - ", {0, 1}> -), <"Main", "concat", 0>, newName = "conc"); + ", {0, 1}) +}, <"Main", "concat", 0>, newName = "conc"); test bool simpleTypeParams() = {0, 1} == testRenameOccurrences(" '&T foo(&T l) = l; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index baa4a543b..ac54bc86f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -30,46 +30,59 @@ import lang::rascal::lsp::refactor::Rename; // Module under test import IO; import List; -import Map; -import Message; +import Location; import Set; import String; import lang::rascal::\syntax::Rascal; // `Name` -import lang::rascalcore::check::Checker; import lang::rascalcore::check::RascalConfig; +import lang::rascalcore::compile::util::Names; import analysis::diff::edits::TextEdits; +import util::FileSystem; import util::Math; import util::Reflective; //// Fixtures and utility functions -alias TestModule = tuple[str body, set[int] expectedRenameOccs]; -bool testRenameOccurrences(map[str, TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { +data TestModule = byText(str name, str body, set[int] expectedRenameOccs, str newName = name) + | byLoc(loc file, set[int] expectedRenameOccs, str newName = name); + +bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { str testName = "Test"; loc testDir = |memory://tests/rename/|; - remove(testDir); + if(any(m <- modules, m is byLoc)) { + testDir = cover([m.file | m <- modules, m is byLoc]); + } else { + // If none of the modules refers to an existing file, clear the test directory before writing files. + remove(testDir); + } - PathConfig pcfg = getTestPathConfig(testName=testName, testDir=testDir); + pcfg = getTestPathConfig(testDir); + modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.expectedRenameOccs, newName = m.newName))}; + cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); - map[loc file, set[int] expectedRenameOccs] modulesByLocation = (l: m.expectedRenameOccs | name <- modules, m := modules[name], l := storeTestModule(testDir, name, m.body)); - return testRenameOccurrences(modulesByLocation, cursor, newName=newName, pcfg=pcfg); -} + edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), pcfg, newName); -bool testRenameOccurrences(map[loc file, set[int] expectedRenameOccs] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar", PathConfig pcfg = getTestWorkspaceConfig()) { - Tree cursorT = findCursor([l | l <- modules, getModuleName(l, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); + editsPerModule = (); + for (changed(f, textedits) <- edits) { + start[Module] m = parseModuleWithSpaces(f); + str name = getModuleName(f, pcfg); + set[loc] locs = {l | replace(l, _) <- textedits}; + set[int] occs = locsToOccs(m, cursor.id, locs); - edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), pcfg, newName); + str newName = name; + if (renamed(f, newLoc) <- edits) { + newName = getModuleName(newLoc, pcfg); + } - editsPerModule = (name: locsToOccs(m, cursor.id, locs) | changed(f, textedits) <- edits - , start[Module] m := parseModuleWithSpaces(f) - , str name := getModuleName(f, pcfg) - , set[loc] locs := {l | replace(l, _) <- textedits}); - expectedEditsPerModule = (name: modules[l] | l <- modules, name := getModuleName(l, pcfg)); + editsPerModule[name] = ; + } + + expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); return editsPerModule == expectedEditsPerModule; } @@ -90,7 +103,7 @@ bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = " return false; } -private PathConfig getTestPathConfig(str testName = "Test", loc testDir = |memory://tests/rename/|) { +private PathConfig getTestPathConfig(loc testDir) { return pathConfig( bin=testDir + "bin", libs=[|lib://rascal|], @@ -107,11 +120,11 @@ private PathConfig getTestWorkspaceConfig() { srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal| , |project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal|], resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generatedSources| + generatedSources=|memory://tests/rename/generated-sources| ); } -list[DocumentEdit] getEdits(loc singleModule, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig()) { +list[DocumentEdit] getEdits(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { loc f = resolveLocation(singleModule); m = parseModuleWithSpaces(f); @@ -124,41 +137,24 @@ list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str old return edits; } -private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule", PathConfig pcfg = getTestPathConfig(testName=moduleName)) { +private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule") { str moduleStr = "module + ' ' 'void main() { ' '}"; - for (src <- pcfg.srcs) { - remove(src.parent); // strip off `/rascal` - } - // Write the file to disk (and clean up later) to easily emulate typical editor behaviour - loc moduleFileName = pcfg.srcs[0] + ".rsc"; + loc testDir = |memory://tests/rename/|; + loc moduleFileName = testDir + "rascal" + ".rsc"; writeFile(moduleFileName, moduleStr); - edits = getEdits(moduleFileName, cursorAtOldNameOccurrence, oldName, newName, pcfg=pcfg); + edits = getEdits(moduleFileName, testDir, cursorAtOldNameOccurrence, oldName, newName); return ; } -private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { - start[Module] m = parseModuleWithSpaces(moduleFileName); - list[loc] oldNameOccurrences = []; - for (/Name n := m, "" == name) { - oldNameOccurrences += n.src; - } - - if ([changed(_, replaces)] := edits) { - repls = {l | replace(l, _) <- replaces}; - return {i | i <- [0..size(oldNameOccurrences)], oldNameOccurrences[i] in repls};; - } else { - throw "Unexpected changes: "; - } -} - private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { start[Module] m = parseModuleWithSpaces(moduleFileName); list[loc] oldNameOccurrences = []; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index d82024861..fdad252a5 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -40,23 +40,23 @@ test bool globalAliasFromUse() = {0, 1, 2, 3} == testRenameOccurrences(" 'y = f(foo); ", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar", cursorAtOldNameOccurrence = 1); -test bool multiModuleAlias() = testRenameOccurrences(( - "alg::Fib": <"alias Foo = int; +test bool multiModuleAlias() = testRenameOccurrences({ + byText("alg::Fib", "alias Foo = int; 'Foo fib(int n) { ' if (n \< 2) { ' return 1; ' } ' return fib(n - 1) + fib(n -2); '}" - , {0, 1}> - , "Main": <"import alg::Fib; + , {0, 1}) + , byText("Main", "import alg::Fib; ' 'int main() { ' Foo result = fib(8); ' return 0; '}" - , {0}> - ), <"Main", "Foo", 0>, newName = "Bar"); + , {0}) + }, <"Main", "Foo", 0>, newName = "Bar"); test bool globalData() = {0, 1, 2, 3} == testRenameOccurrences(" 'Foo f(Foo x) = x; @@ -64,17 +64,17 @@ test bool globalData() = {0, 1, 2, 3} == testRenameOccurrences(" 'y = f(x); ", decls = "data Foo = foo();", oldName = "Foo", newName = "Bar"); -test bool multiModuleData() = testRenameOccurrences(( - "values::Bool": <" +test bool multiModuleData() = testRenameOccurrences({ + byText("values::Bool", " 'data Bool = t() | f(); ' 'Bool and(Bool l, Bool r) = r is t ? l : f; '" - , {1, 2, 3, 4}> - , "Main": <"import values::Bool; + , {1, 2, 3, 4}) + , byText("Main", "import values::Bool; ' 'void main() { ' Bool b = and(t(), f()); '}" - , {1}> - ), <"Main", "Bool", 1>, newName = "Boolean"); + , {1}) + }, <"Main", "Bool", 1>, newName = "Boolean"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc index 81811b11c..137031639 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -179,8 +179,8 @@ test bool globalVar() = {0, 3} == testRenameOccurrences(" 'int foo = 8; "); -test bool multiModuleVar() = testRenameOccurrences(( - "alg::Fib": <"int foo = 8; +test bool multiModuleVar() = testRenameOccurrences({ + byText("alg::Fib", "int foo = 8; ' 'int fib(int n) { ' if (n \< 2) { @@ -188,12 +188,12 @@ test bool multiModuleVar() = testRenameOccurrences(( ' } ' return fib(n - 1) + fib(n -2); '}" - , {0}> - , "Main": <"import alg::Fib; + , {0}) + , byText("Main", "import alg::Fib; ' 'int main() { ' fib(alg::Fib::foo); ' return 0; '}" - , {0}> - ), <"Main", "foo", 0>, newName = "Bar"); + , {0}) + }, <"Main", "foo", 0>, newName = "Bar"); From 3cf28af3c8230a26aa34caf8f955865457110d33 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 17 Jul 2024 17:25:28 +0200 Subject: [PATCH 029/105] Per-project path config, lazily loaded. --- .../lsp/rascal/RascalLanguageServices.java | 17 ++++++++++--- .../lsp/rascal/RascalTextDocumentService.java | 2 +- .../lang/rascal/lsp/refactor/Rename.rsc | 4 ++-- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 24 +++++++++++-------- .../lang/rascal/tests/rename/TestUtils.rsc | 4 ++-- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java index 233a6c2b7..5f532d5d8 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java @@ -39,6 +39,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -48,6 +49,7 @@ import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.values.functions.IFunction; import org.rascalmpl.values.parsetrees.ITree; import org.rascalmpl.values.parsetrees.TreeAdapter; import org.rascalmpl.vscode.lsp.BaseWorkspaceService; @@ -64,6 +66,9 @@ import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; +import io.usethesource.vallang.type.Type; +import io.usethesource.vallang.type.TypeFactory; +import io.usethesource.vallang.type.TypeStore; public class RascalLanguageServices { private static final IValueFactory VF = IRascalValueFactory.getInstance(); @@ -74,6 +79,10 @@ public class RascalLanguageServices { private final CompletableFuture semanticEvaluator; private final CompletableFuture compilerEvaluator; + private final TypeFactory tf = TypeFactory.getInstance(); + private final TypeStore store = new TypeStore(); + private final Type getPathConfigType = tf.functionType(tf.abstractDataType(store, "PathConfig"), tf.tupleType(tf.sourceLocationType()), tf.tupleEmpty()); + private final ExecutorService exec; public RascalLanguageServices(RascalTextDocumentService docService, BaseWorkspaceService workspaceService, IBaseLanguageClient client, ExecutorService exec) { @@ -177,14 +186,16 @@ public InterruptibleFuture getOutline(IConstructor module) { } - public InterruptibleFuture getRename(ITree module, Position cursor, Set workspaceFolders, PathConfig pcfg, String newName, ColumnMaps columns) { + public InterruptibleFuture getRename(ITree module, Position cursor, Set workspaceFolders, Function getPathConfig, String newName, ColumnMaps columns) { var line = cursor.getLine() + 1; var moduleLocation = TreeAdapter.getLocation(module); var translatedOffset = columns.get(moduleLocation).translateInverseColumn(line, cursor.getCharacter(), false); var cursorTree = TreeAdapter.locateLexical(module, line, translatedOffset); - return runEvaluator("Rascal rename", semanticEvaluator, eval -> (IList) eval.call("renameRascalSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), addResources(pcfg), VF.string(newName)), - VF.list(), exec); + return runEvaluator("Rascal rename", semanticEvaluator, eval -> { + IFunction rascalGetPathConfig = eval.getFunctionValueFactory().function(getPathConfigType, (t, u) -> addResources(getPathConfig.apply((ISourceLocation) t[0]))); + return (IList) eval.call("renameRascalSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), VF.string(newName), rascalGetPathConfig); + }, VF.list(), exec); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java index 2de0504c3..54be24c54 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java @@ -286,7 +286,7 @@ public CompletableFuture rename(RenameParams params) { return file.getCurrentTreeAsync() .thenApply(Versioned::get) .handle((t, r) -> (t == null ? (file.getMostRecentTree().get()) : t)) - .thenCompose(tr -> rascalServices.getRename(tr, params.getPosition(), workspaceFolders, facts.getPathConfig(file.getLocation()), params.getNewName(), columns).get()) + .thenCompose(tr -> rascalServices.getRename(tr, params.getPosition(), workspaceFolders, facts::getPathConfig, params.getNewName(), columns).get()) .thenApply(c -> new WorkspaceEdit(DocumentChanges.translateDocumentChanges(this, c))) ; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index c05e31316..348b390ff 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -259,8 +259,8 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, return changes + renames; } -list[DocumentEdit] renameRascalSymbol(Tree cursor, set[loc] workspaceFolders, PathConfig pcfg, str newName) { - WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, pcfg); +list[DocumentEdit] renameRascalSymbol(Tree cursor, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) { + WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, getPathConfig); return computeDocumentEdits(ws, cursor, newName); } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 8edecba33..92e713e76 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -59,7 +59,7 @@ data WorkspaceInfo ( set[loc] modules = {}, map[loc, Define] definitions = (), map[loc, AType] facts = () -) = workspaceInfo(set[loc] folders, PathConfig pcfg); +) = workspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig); private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { ws.useDef += tm.useDef; @@ -76,17 +76,21 @@ private void checkNoErrors(ModuleStatus ms) { throw unsupportedRename(errors); } -WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig pcfg) { - ws = workspaceInfo(folders, pcfg); +WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig) { + ws = workspaceInfo(folders, getPathConfig); - mods = [m | f <- folders, m <- find(f, "rsc")]; - ms = rascalTModelForLocs(mods, getRascalCoreCompilerConfig(pcfg), dummy_compile1); - checkNoErrors(ms); + for (f <- folders) { + PathConfig pcfg = getPathConfig(f); + RascalCompilerConfig ccfg = getRascalCoreCompilerConfig(pcfg); - for (m <- ms.tmodels) { - tm = convertTModel2PhysicalLocs(ms.tmodels[m]); - ws = loadModel(ws, tm); - ws.modules += {ms.moduleLocs[m].top}; + ms = rascalTModelForLocs(toList(find(f, "rsc")), ccfg, dummy_compile1); + checkNoErrors(ms); + + for (m <- ms.tmodels) { + tm = convertTModel2PhysicalLocs(ms.tmodels[m]); + ws = loadModel(ws, tm); + ws.modules += {ms.moduleLocs[m].top}; + } } return ws; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index ac54bc86f..ee3206b96 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -65,7 +65,7 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.expectedRenameOccs, newName = m.newName))}; cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); - edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), pcfg, newName); + edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); editsPerModule = (); for (changed(f, textedits) <- edits) { @@ -129,7 +129,7 @@ list[DocumentEdit] getEdits(loc singleModule, loc projectDir, int cursorAtOldNam m = parseModuleWithSpaces(f); Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(cursor, toSet(pcfg.srcs), pcfg, newName); + return renameRascalSymbol(cursor, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); } list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports) { From abd16b9bda4766942d0efb5e4cd910fd2e2067cb Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Jul 2024 15:32:15 +0200 Subject: [PATCH 030/105] Pass message in unsupported rename exception. --- .../src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc | 5 +---- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 6 +++--- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index cb7226b23..aa961980a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -26,8 +26,6 @@ POSSIBILITY OF SUCH DAMAGE. } module lang::rascal::lsp::refactor::Exception -import Message; - data Cursor; alias Capture = tuple[loc def, loc use]; @@ -41,7 +39,6 @@ data IllegalRenameReason data RenameException = illegalRename(Cursor cursor, set[IllegalRenameReason] reason) - | unsupportedRename(rel[loc location, str message] issues) - | unsupportedRename(list[Message] msgs) + | unsupportedRename(str message, rel[loc location, str message] issues = {}) | unexpectedFailure(str message) ; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 348b390ff..85fe0d0ae 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -152,7 +152,7 @@ private set[loc] findNames(start[Module] m, set[loc] useDefs) { } if (size(names) != size(useDefs)) { - throw unsupportedRename({."> | l <- useDefs - names}); + throw unsupportedRename("Rename unsupported", issues={."> | l <- useDefs - names}); } return names; @@ -208,7 +208,7 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, }; if (size(locsContainingCursor) == 0) { - throw unsupportedRename({}); + throw unsupportedRename("Cannot find type information in TPL for "); } loc c = min(locsContainingCursor.l); @@ -233,7 +233,7 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, cur = cursor(k, c, cursorName); } default: - throw unsupportedRename({">}); + throw unsupportedRename("Unsupported cursor type "); } if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 92e713e76..b1fe13a5d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -73,7 +73,7 @@ private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { private void checkNoErrors(ModuleStatus ms) { errors = [msg | m <- ms.messages, msg <- ms.messages[m], msg is error]; if (errors != []) - throw unsupportedRename(errors); + throw unsupportedRename("Cannot rename; some modules in workspace have errors.", issues={ | error(reason, l) <- errors}); } WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig) { @@ -161,5 +161,5 @@ tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), curs return ; } - throw unsupportedRename({\'">}); + throw unsupportedRename("Cannot find function definition which defines template variable \'\'"); } From 3efcaf9ef507edd30b9cf149a8971354ce842881 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Jul 2024 15:33:59 +0200 Subject: [PATCH 031/105] Implement some field renames; don't support others for now. --- .../lang/rascal/lsp/refactor/Rename.rsc | 17 +++- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 4 +- .../lang/rascal/tests/rename/Fields.rsc | 77 ++++++++++++++++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 85fe0d0ae..12df0d061 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -204,6 +204,7 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, , cursorLoc), use()> , , + , } }; @@ -222,9 +223,14 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, if (size(getDefs(ws, c) & ws.defines.defined) > 0) { // The cursor is at a use with corresponding definitions. cur = cursor(use(), c, cursorName); - } else if (ws.facts[c]? && aparameter(cursorName, _) := ws.facts[c]) { - // The cursor is at a type parameter - cur = cursor(typeParam(), c, cursorName); + } else if (ws.facts[c]?) { + if (aparameter(cursorName, _) := ws.facts[c]) { + // The cursor is at a type parameter + cur = cursor(typeParam(), c, cursorName); + } else if (ws.facts[c].alabel == cursorName) { + // The cursor is at a collection field + cur = cursor(collectionField(), c, cursorName); + } } else { fail; } @@ -238,6 +244,11 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); + switch (cur.kind) { + case collectionField(): + throw unsupportedRename("Renaming fields of collections is not supported"); + } + = getDefsUses(ws, cur, rascalMayOverloadSameName); rel[loc file, loc defines] defsPerFile = { | d <- defs}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index b1fe13a5d..ba119ab25 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -47,7 +47,9 @@ import String; data CursorKind = use() | def() - | typeParam(); + | typeParam() + | collectionField() + ; data Cursor = cursor(CursorKind kind, loc l, str name); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 4853ee8d0..227a36f94 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -26,5 +26,78 @@ POSSIBILITY OF SUCH DAMAGE. } module lang::rascal::tests::rename::Fields -// import lang::rascal::tests::rename::TestUtils; -// import lang::rascal::lsp::refactor::Exception; +import lang::rascal::tests::rename::TestUtils; +import lang::rascal::lsp::refactor::Exception; + +test bool dataFieldAtDef() = {0, 1} == testRenameOccurrences(" + 'D oneTwo = d(1, 2); + 'x = d.foo; + ", decls = "data D = d(int foo, int bar);" +); + +test bool dataFieldAtUse() = {0, 1} == testRenameOccurrences(" + 'D oneTwo = d(1, 2); + 'x = d.foo; + ", decls = "data D = d(int foo, int bar);" +, cursorAtOldNameOccurrence = 1); + +test bool duplicateDataField() = {0, 1, 2} == testRenameOccurrences(" + 'x = d(1, 2); + 'y = x.foo; + ", decls = "data D = d(int foo) | d(int foo, int bar);" +, cursorAtOldNameOccurrence = 1); + +test bool crossModuleDataFieldAtDef() = testRenameOccurrences({ + byText("Foo", "data D = a(int foo) | b(int bar);", {0}), + byText("Main", " + 'import Foo; + 'void main() { + ' f = a(8); + ' g = a.foo; + '} + ", {0}) +}, <"Foo", "foo", 0>); + +test bool crossModuleDataFieldAtUse() = testRenameOccurrences({ + byText("Foo", "data D = a(int foo) | b(int bar);", {0}), + byText("Main", " + 'import Foo; + 'void main() { + ' f = a(8); + ' g = a.foo; + '} + ", {0}) +}, <"Main", "foo", 0>); + +@expected{unsupportedRename} +test bool relFieldAtDef() = {0, 1} == testRenameOccurrences(" + 'rel[str foo, str baz] r1 = {}; + 'foos = r1.foo; + 'rel[str foo, str baz] r2 = {}; +"); + +@expected{unsupportedRename} +test bool relFieldAtUse() = {0, 1} == testRenameOccurrences(" + 'rel[str foo, str baz] r1 = {}; + 'foos = r1.foo; + 'rel[str foo, str baz] r2 = {}; +", cursorAtOldNameOccurrence = 1); + +@expected{unsupportedRename} +test bool tupleFieldAtDef() = {0, 1} == testRenameOccurrences(" + 'tuple[int foo] t = \<8\>; + 'y = t.foo; +"); + +@expected{unsupportedRename} +test bool tupleFieldAtUse() = {0, 1} == testRenameOccurrences(" + 'tuple[int foo] t = \<8\>; + 'y = t.foo; +", cursorAtOldNameOccurrence = 1); + +// We would prefer an illegalRename exception here +@expected{unsupportedRename} +test bool builtinField() = testRename(" + 'loc l = |unknown:///|; + 'f = l.top; +", oldName = "top", newName = "summit"); From e2a63351f8692f1763ce243ddffb360f8b40ee98 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Jul 2024 15:36:27 +0200 Subject: [PATCH 032/105] Small fixes to trim util. --- .../src/main/rascal/lang/rascal/lsp/refactor/Util.rsc | 6 ++++-- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 599361545..961ece96b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -38,8 +38,10 @@ loc min(list[loc] locs) = (getFirstFrom(locs) | l < l ? l : it | l <- locs); loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { - return l[offset = l.offset + removePrefix - removeSuffix] + assert l.begin.line == l.end.line : + "Cannot trim a multi-line location"; + return l[offset = l.offset + removePrefix] [length = l.length - removePrefix - removeSuffix] [begin = ] - [end = ]; + [end = ]; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index ba119ab25..5bdffb554 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -152,12 +152,11 @@ tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), curs loc sentinel = |unknown:///|(0, 0, <0, 0>, <0, 0>); defs = {(sentinel | (it == sentinel || f.length < it.length) && f.offset == nextOffset ? f : it | f <- facts<0>) | formal <- formals, nextOffsets[formal.offset]?, nextOffset := nextOffsets[formal.offset]}; - useDefs = {trim(l, removePrefix=prefixLength) + useDefs = {trim(l, removePrefix = l.length - size(cursorName)) | l <- facts<0> , !ws.definitions[l]? // If there is a definition at this location, this is a formal argument name , !any(ud <- ws.useDef[l], ws.definitions[ud]?) // If there is a definition for the use at this location, this is a use of a formal argument , !any(flInner <- facts<0>, isStrictlyContainedIn(flInner, l)) // Filter out any facts that contain other facts - , prefixLength := l.length - size(cursorName) }; return ; From 5b697830127c330618d4fa22480a4b0e58b6f731 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Jul 2024 15:47:44 +0200 Subject: [PATCH 033/105] Fix type errors. --- .../src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc index 349fd0cda..b342ce294 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/ValidNames.rsc @@ -29,6 +29,8 @@ module lang::rascal::tests::rename::ValidNames import lang::rascal::tests::rename::TestUtils; import lang::rascal::lsp::refactor::Exception; +import analysis::diff::edits::TextEdits; + test bool renameToReservedName() { edits = getEdits("int foo = 8;", 0, "foo", "int", "", ""); newNames = {name | e <- edits, changed(_, replaces) := e From a61d5588dfaac1e16fb7782044751ec7fc323208 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Jul 2024 16:07:59 +0200 Subject: [PATCH 034/105] Add tests for annotations (already implemented). --- .../lang/rascal/tests/rename/Annotations.rsc | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc new file mode 100644 index 000000000..24c7f6214 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc @@ -0,0 +1,60 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Annotations + +import lang::rascal::tests::rename::TestUtils; +import lang::rascal::lsp::refactor::Exception; + +test bool annoAtDecl() = {0, 1} == testRenameOccurrences(" + 'x = d(); + 'x@foo = 8; + ", decls = " + 'data D = d(); + 'anno int D@foo; +"); + +test bool annoAtUse() = {0, 1} == testRenameOccurrences(" + 'x = d(); + 'int i = x@foo; + ", decls = " + 'data D = d(); + 'anno int D@foo; +", cursorAtOldNameOccurrence = 1); + +test bool annoAtAssign() = {0, 1} == testRenameOccurrences(" + 'x = d(); + 'x@foo = 8; + ", decls = " + 'data D = d(); + 'anno int D@foo; +", cursorAtOldNameOccurrence = 1); + +@expected{illegalRename} +test bool builtinAnno() = {0, 1} == testRenameOccurrences(" + 'Tree t = char(8); + 'x = t@\\loc; +", oldName = "\\loc", imports = "import ParseTree;"); From 912c24b73b29a36d58213894e409ae37abbacfb8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 18 Jul 2024 17:11:33 +0200 Subject: [PATCH 035/105] Only ignore specific files for compilation. --- rascal-lsp/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml index cd6fc3d56..501462d0b 100644 --- a/rascal-lsp/pom.xml +++ b/rascal-lsp/pom.xml @@ -169,7 +169,8 @@ ${project.basedir}/src/main/rascal - ${project.basedir}/src/main/rascal/lang/rascal/lsp/Refactor + ${project.basedir}/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc + ${project.basedir}/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc ${project.basedir}/src/main/rascal/lang/rascal/tests/rename |lib://rascal-lsp| From 9d70671e358d58e3b402c51f9d32e7bf940c8e23 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 19 Jul 2024 12:15:08 +0200 Subject: [PATCH 036/105] Report progress and errors to the user. --- .../lang/rascal/lsp/refactor/Rename.rsc | 19 +++++++++++-------- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 12 ++++++++++++ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 15 ++++++++++----- .../lang/rascal/tests/rename/Performance.rsc | 3 +++ 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 12df0d061..7b475245a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -60,6 +60,7 @@ import analysis::diff::edits::TextEdits; import vis::Text; import util::Maybe; +import util::Monitor; import util::Reflective; set[IllegalRenameReason] checkLegalName(str name) { @@ -190,7 +191,11 @@ private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitio return rascalMayOverload(defs, potentialOverloadDefinitions); } -private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, str name) { +list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { + step("collecting workspace information", 1); + WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, getPathConfig); + + step("analyzing name at cursor", 1); loc cursorLoc = cursorT.src; str cursorName = ""; @@ -249,14 +254,17 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, throw unsupportedRename("Renaming fields of collections is not supported"); } + step("collecting uses of \'\'", 1); = getDefsUses(ws, cur, rascalMayOverloadSameName); rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; files = defsPerFile.file + usesPerFile.file; + + step("checking rename validity", 1); map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults = - (file: | file <- files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], name)); + (file: | file <- files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName)); if (reasons := union({moduleResults[file].reasons | file <- moduleResults}), reasons != {}) { throw illegalRename(cur, reasons); @@ -268,12 +276,7 @@ private list[DocumentEdit] computeDocumentEdits(WorkspaceInfo ws, Tree cursorT, list[DocumentEdit] renames = []; return changes + renames; -} - -list[DocumentEdit] renameRascalSymbol(Tree cursor, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) { - WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, getPathConfig); - return computeDocumentEdits(ws, cursor, newName); -} +}, totalWork = 4); //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 961ece96b..1cef87be7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -28,6 +28,7 @@ module lang::rascal::lsp::refactor::Util import List; import Location; +import Message; import util::Maybe; @@ -45,3 +46,14 @@ loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { [begin = ] [end = ]; } + +str toString(error(msg, l)) = "[error] \'\' at "; +str toString(error(msg)) = "[error] \'\'"; +str toString(warning(msg, l)) = "[warning] \'\' at "; +str toString(info(msg, l)) = "[info] \'\' at "; + +str toString(list[Message] msgs, int indent = 1) = + intercalate("\n", ([] | it + " <}>- " | msg <- msgs)); + +str toString(map[str, list[Message]] moduleMsgs) = + intercalate("\n", ([] | it + "Messages for :\n" | m <- moduleMsgs)); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 5bdffb554..fbdc2073e 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -33,6 +33,7 @@ import analysis::typepal::TModel; import lang::rascalcore::check::Checker; import util::FileSystem; +import util::Monitor; import util::Reflective; import lang::rascal::lsp::refactor::Exception; @@ -40,6 +41,7 @@ import lang::rascal::lsp::refactor::Util; import List; import Location; +import Map; import Message; import Set; import String; @@ -73,15 +75,18 @@ private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { } private void checkNoErrors(ModuleStatus ms) { - errors = [msg | m <- ms.messages, msg <- ms.messages[m], msg is error]; - if (errors != []) - throw unsupportedRename("Cannot rename; some modules in workspace have errors.", issues={ | error(reason, l) <- errors}); + errors = (m: msgs | m <- ms.messages + , msgs := [msg | msg <- ms.messages[m], msg is error] + , msgs != []); + if (errors != ()) + throw unsupportedRename("Cannot rename: some modules in workspace have errors.\n", issues={<(error.at ? |unknown:///|), error.msg> | m <- errors, error <- errors[m]}); } -WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig) { +WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig) = job("loading workspace information", WorkspaceInfo(void(str, int) step) { ws = workspaceInfo(folders, getPathConfig); for (f <- folders) { + step("loading modules for project \'\'", 1); PathConfig pcfg = getPathConfig(f); RascalCompilerConfig ccfg = getRascalCoreCompilerConfig(pcfg); @@ -96,7 +101,7 @@ WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfi } return ws; -} +}, totalWork = size(folders)); @memo set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc index 161fcc86d..43eff0aef 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc @@ -36,3 +36,6 @@ test bool largeTest() = ({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LA 'foo = foo + foo;" | i <- [0..LARGE_TEST_SIZE]) ); + +@expected{unsupportedRename} +test bool failOnError() = testRename("int foo = x + y;"); From 5d9aa69a4f0380b768f0834aa9ca083ec9fcfd97 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 19 Jul 2024 12:15:39 +0200 Subject: [PATCH 037/105] Test data field rename across extends. --- .../main/rascal/lang/rascal/tests/rename/Fields.rsc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 227a36f94..5d9644f49 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -69,6 +69,16 @@ test bool crossModuleDataFieldAtUse() = testRenameOccurrences({ ", {0}) }, <"Main", "foo", 0>); +test bool extendedDataField() = testRenameOccurrences({ + byText("Scratch1", " + 'data Foo = f(int foo); + ", {0}), + byText("Scratch2", " + 'extend Scratch1; + 'data Foo = g(int foo); + ", {0}) +}, <"Scratch2", "foo", 0>); + @expected{unsupportedRename} test bool relFieldAtDef() = {0, 1} == testRenameOccurrences(" 'rel[str foo, str baz] r1 = {}; From a99cc19917bc3b0c166da963644145e2540626ce Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 22 Jul 2024 09:31:30 +0200 Subject: [PATCH 038/105] Crawl modules in source folders only. --- .../rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index fbdc2073e..72c60348f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -85,12 +85,12 @@ private void checkNoErrors(ModuleStatus ms) { WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig) = job("loading workspace information", WorkspaceInfo(void(str, int) step) { ws = workspaceInfo(folders, getPathConfig); - for (f <- folders) { - step("loading modules for project \'\'", 1); - PathConfig pcfg = getPathConfig(f); + for (projectFolder <- folders) { + step("loading modules for project \'\'", 1); + PathConfig pcfg = getPathConfig(projectFolder); RascalCompilerConfig ccfg = getRascalCoreCompilerConfig(pcfg); - ms = rascalTModelForLocs(toList(find(f, "rsc")), ccfg, dummy_compile1); + ms = rascalTModelForLocs([file | srcFolder <- pcfg.srcs, file <- find(srcFolder, "rsc")], ccfg, dummy_compile1); checkNoErrors(ms); for (m <- ms.tmodels) { From 331d2b4e50060e268a4f2371ded851611daebc9f Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 24 Jul 2024 18:33:52 +0200 Subject: [PATCH 039/105] Load TPLs for selected modules only. Only load TPLs for modules that contain the name under the cursor. This prevents type-checking or loading any modules that will not play a role in the renaming anyway, at the cost of parsing all modules (which is fast) and looking for specific names (which is also relatively fast). Since type-checking is relatively slow, this should improve performance greatly, especially for large projects and rare names. --- .../lang/rascal/lsp/refactor/Rename.rsc | 20 ++++++++++++++++--- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 10 ++++++++++ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 19 ++++++++++-------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 7b475245a..e80ac7f89 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -193,14 +193,28 @@ private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitio list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { step("collecting workspace information", 1); - WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, getPathConfig); - step("analyzing name at cursor", 1); loc cursorLoc = cursorT.src; str cursorName = ""; - println("Cursor is at id \'\' at "); + cursorAsName = [Name] cursorName; + escapedCursorAsName = startsWith(cursorName, "\\") ? cursorName : [Name] "\\"; + + WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, getPathConfig, fileFilter = bool(loc l) { + // If we do not find any occurrences of the name under the cursor in a module, + // we are not interested in it at all, and will skip loading its TPL. + m = parseModuleWithSpacesCached(l); + if (/cursorAsName := m) { + return true; + } else if (escapedCursorAsName != cursorAsName, /escapedCursorAsName := m) { + return true; + } + return false; + }); + + step("analyzing name at cursor", 1); + cursorNamedDefs = (ws.defines)[cursorName]; rel[loc l, CursorKind kind] locsContainingCursor = { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 1cef87be7..9642682fe 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -26,11 +26,16 @@ POSSIBILITY OF SUCH DAMAGE. } module lang::rascal::lsp::refactor::Util +import IO; import List; import Location; import Message; import util::Maybe; +import util::Memo; +import util::Reflective; + +import lang::rascal::\syntax::Rascal; Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) = (nothing() | (it == nothing() || (just(itt) := it && w < itt)) && isContainedIn(l, w) ? just(w) : it | w <- wrappers); @@ -47,6 +52,11 @@ loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { [end = ]; } +start[Module] parseModuleWithSpacesCached(loc l) { + @memo{expireAfter(minutes=5)} start[Module] parseModuleWithSpacesCached(loc l, datetime _) = parseModuleWithSpaces(l); + return parseModuleWithSpacesCached(l, lastModified(l)); +} + str toString(error(msg, l)) = "[error] \'\' at "; str toString(error(msg)) = "[error] \'\'"; str toString(warning(msg, l)) = "[warning] \'\' at "; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 72c60348f..867057ef4 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -31,6 +31,7 @@ import Relation; import analysis::typepal::TModel; import lang::rascalcore::check::Checker; +import lang::rascalcore::check::RascalConfig; import util::FileSystem; import util::Monitor; @@ -63,7 +64,7 @@ data WorkspaceInfo ( set[loc] modules = {}, map[loc, Define] definitions = (), map[loc, AType] facts = () -) = workspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig); +) = workspaceInfo(set[loc] folders); private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { ws.useDef += tm.useDef; @@ -82,15 +83,20 @@ private void checkNoErrors(ModuleStatus ms) { throw unsupportedRename("Cannot rename: some modules in workspace have errors.\n", issues={<(error.at ? |unknown:///|), error.msg> | m <- errors, error <- errors[m]}); } -WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig) = job("loading workspace information", WorkspaceInfo(void(str, int) step) { - ws = workspaceInfo(folders, getPathConfig); +WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig, bool(loc) fileFilter = bool(loc l) { return true; }) = job("loading workspace information", WorkspaceInfo(void(str, int) step) { + ws = workspaceInfo(folders); for (projectFolder <- folders) { step("loading modules for project \'\'", 1); PathConfig pcfg = getPathConfig(projectFolder); - RascalCompilerConfig ccfg = getRascalCoreCompilerConfig(pcfg); - ms = rascalTModelForLocs([file | srcFolder <- pcfg.srcs, file <- find(srcFolder, "rsc")], ccfg, dummy_compile1); + fs = [file | srcFolder <- pcfg.srcs, file <- find(srcFolder, "rsc"), fileFilter(file)]; + + if (fs == []) + continue; + + RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true][verbose = false][logPathConfig = false]; + ms = rascalTModelForLocs(fs, ccfg, dummy_compile1); checkNoErrors(ms); for (m <- ms.tmodels) { @@ -103,13 +109,10 @@ WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfi return ws; }, totalWork = size(folders)); -@memo set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; -@memo set[loc] getUses(WorkspaceInfo ws, set[loc] defs) = invert(ws.useDef)[defs]; -@memo set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { From 229d7a640ea2c0b0c9671ef8a1879ab2400b1855 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 24 Jul 2024 18:36:56 +0200 Subject: [PATCH 040/105] Rename evaluator. --- .../org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java index ca788b5a3..504f631e8 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java @@ -95,7 +95,7 @@ public RascalLanguageServices(RascalTextDocumentService docService, BaseWorkspac var monitor = new RascalLSPMonitor(client, logger); outlineEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal outline", monitor, null, false, "lang::rascal::lsp::Outline"); - semanticEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal summary", monitor, null, true, "lang::rascalcore::check::Summary", "lang::rascal::lsp::refactor::Rename"); + semanticEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal semantics", monitor, null, true, "lang::rascalcore::check::Summary", "lang::rascal::lsp::refactor::Rename"); compilerEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal compiler", monitor, null, true, "lang::rascalcore::check::Checker"); } From 2c26259ded6b30bef5e4cc557567f99669593fff Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 25 Jul 2024 13:43:38 +0200 Subject: [PATCH 041/105] Implement all field types. --- .../lang/rascal/lsp/refactor/Rename.rsc | 7 +- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 5 +- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 80 +++++++++++++++++++ .../lang/rascal/tests/rename/Fields.rsc | 77 +++++++++++++++--- 4 files changed, 151 insertions(+), 18 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index e80ac7f89..b9fbca2a4 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -223,7 +223,7 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s , cursorLoc), use()> , , - , + , } }; @@ -263,11 +263,6 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); - switch (cur.kind) { - case collectionField(): - throw unsupportedRename("Renaming fields of collections is not supported"); - } - step("collecting uses of \'\'", 1); = getDefsUses(ws, cur, rascalMayOverloadSameName); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 9642682fe..53fa8ab9d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -32,7 +32,6 @@ import Location; import Message; import util::Maybe; -import util::Memo; import util::Reflective; import lang::rascal::\syntax::Rascal; @@ -67,3 +66,7 @@ str toString(list[Message] msgs, int indent = 1) = str toString(map[str, list[Message]] moduleMsgs) = intercalate("\n", ([] | it + "Messages for :\n" | m <- moduleMsgs)); + +rel[&K, &V] groupBy(set[&V] s, &K(&V) pred) = + { | v <- s}; + diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 867057ef4..4b02beffc 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -24,6 +24,7 @@ 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 lang::rascal::lsp::refactor::WorkspaceInfo import Relation; @@ -33,13 +34,17 @@ import analysis::typepal::TModel; import lang::rascalcore::check::Checker; import lang::rascalcore::check::RascalConfig; +import lang::rascal::\syntax::Rascal; + import util::FileSystem; +import util::Maybe; import util::Monitor; import util::Reflective; import lang::rascal::lsp::refactor::Exception; import lang::rascal::lsp::refactor::Util; +import IO; import List; import Location; import Map; @@ -172,3 +177,78 @@ tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), curs throw unsupportedRename("Cannot find function definition which defines template variable \'\'"); } + +tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _) { + AType cursorType = ws.facts[cursorLoc]; + println("Cursor type: "); + factLocsSortedBySize = sort(domain(ws.facts), bool(loc l1, loc l2) { return l1.length < l2.length; }); + + AType fieldType = avoid(); + AType collectionType = avoid(); + if (l <- factLocsSortedBySize, isStrictlyContainedIn(cursorLoc, l), at := ws.facts[l], (at is arel || at is alrel || at is atuple), at.elemType is atypeList) { + // We are at a definition site + collectionType = at; + fieldType = cursorType; + } + else { + // We are at a use site, where the field element type is wrapped in a `aset` of `alist` constructor + fieldType = cursorType.alabel == "" + // Collection type + ? cursorType.elmType + // Tuple type + : cursorType; + + // We need to find the collection type by looking for the first use to the left of the cursor that has a collection type + usesToLeft = reverse(sort({ | <- ws.useDef, isSameFile(u, cursorLoc), u.offset < cursorLoc.offset})); + if (<_, d> <- usesToLeft, define := ws.definitions[d], defType(AType at) := define.defInfo, (at is arel || at is alrel || at is atuple)) { + collectionType = at; + } else { + throw unsupportedRename("Could not find a collection definition corresponding to the field at the cursor."); + } + } + + set[loc] collectionFacts = invert(ws.facts)[collectionType]; + rel[loc file, loc u] factsByModule = groupBy(collectionFacts, loc(loc l) { return l.top; }); + + set[loc] defs = {}; + set[loc] uses = {}; + for (file <- factsByModule.file) { + fileFacts = factsByModule[file]; + visit(parseModuleWithSpacesCached(file)) { + case (Expression) `.`: { + if ("" == cursorName && any(f <- fileFacts, isContainedIn(f, e.src))) { + uses += field.src; + } + } + case (Assignable) `.`: { + if ("" == cursorName && any(f <- fileFacts, isContainedIn(f, rec.src))) { + uses += field.src; + } + } + case (Expression) `\< <{Field ","}+ fields> \>`: { + if (any(f <- fileFacts, isContainedIn(e.src, f))) { + uses += {field.src | field <- fields + , field is name + , "" == cursorName}; + } + } + case (Expression) `[ = ]`: { + if ("" == cursorName && any(f <- fileFacts, isContainedIn(f, e.src))) { + uses += field.src; + } + } + case t:(StructuredType) `[<{TypeArg ","}+ args>]`: { + for (at := ws.facts[t.src], collectionType.elemType == at.elemType, (TypeArg) ` ` <- args, fieldType == ws.facts[name.src]) { + defs += name.src; + } + } + } + } + + if (defs == {}) { + throw unsupportedRename("Cannot rename field that is not declared inside this workspace."); + } + + return ; +} + diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 5d9644f49..2bbd58c72 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -79,27 +79,75 @@ test bool extendedDataField() = testRenameOccurrences({ ", {0}) }, <"Scratch2", "foo", 0>); -@expected{unsupportedRename} test bool relFieldAtDef() = {0, 1} == testRenameOccurrences(" - 'rel[str foo, str baz] r1 = {}; - 'foos = r1.foo; - 'rel[str foo, str baz] r2 = {}; + 'rel[str foo, str baz] r = {}; + 'f = r.foo; "); -@expected{unsupportedRename} test bool relFieldAtUse() = {0, 1} == testRenameOccurrences(" - 'rel[str foo, str baz] r1 = {}; - 'foos = r1.foo; - 'rel[str foo, str baz] r2 = {}; + 'rel[str foo, str baz] r = {}; + 'f = r.foo; +", cursorAtOldNameOccurrence = 1); + +test bool lrelFieldAtDef() = {0, 1} == testRenameOccurrences(" + 'lrel[str foo, str baz] r = []; + 'f = r.foo; +"); + +test bool lrelFieldAtUse() = {0, 1} == testRenameOccurrences(" + 'lrel[str foo, str baz] r = []; + 'foos = r.foo; +", cursorAtOldNameOccurrence = 1); + +test bool relSubscript() = {0, 1} == testRenameOccurrences(" + 'rel[str foo, str baz] r = {}; + 'x = r\; +"); + +test bool relSubscriptWithVar() = {0, 2} == testRenameOccurrences(" + 'rel[str foo, str baz] r = {}; + 'str foo = \"foo\"; + 'x = r\; +"); + +test bool tupleFieldSubscriptUpdate() = {0, 1, 2} == testRenameOccurrences(" + 'tuple[str foo, int baz] t = \<\"one\", 1\>; + 'u = t[foo = \"two\"]; + 'v = u.foo; +"); + +test bool tupleFieldAccessUpdate() = {0, 1} == testRenameOccurrences(" + 'tuple[str foo, int baz] t = \<\"one\", 1\>; + 't.foo = \"two\"; +"); + +test bool similarCollectionTypes() = {0, 1, 2, 3, 4} == testRenameOccurrences(" + 'rel[str foo, int baz] r = {}; + 'lrel[str foo, int baz] lr = []; + 'set[tuple[str foo, int baz]] st = {}; + 'list[tuple[str foo, int baz]] lt = []; + 'tuple[str foo, int baz] t = \<\"\", 0\>; +"); + +test bool differentRelWithSameFieldAtDef() = {0, 1} == testRenameOccurrences(" + 'rel[str foo, int baz] r1 = {}; + 'foos1 = r1.foo; + 'rel[int n, str foo] r2 = {}; + 'foos2 = r2.foo; +"); + +test bool differentRelWithSameFieldAtUse() = {0, 1} == testRenameOccurrences(" + 'rel[str foo, int baz] r1 = {}; + 'foos1 = r1.foo; + 'rel[int n, str foo] r2 = {}; + 'foos2 = r2.foo; ", cursorAtOldNameOccurrence = 1); -@expected{unsupportedRename} test bool tupleFieldAtDef() = {0, 1} == testRenameOccurrences(" 'tuple[int foo] t = \<8\>; 'y = t.foo; "); -@expected{unsupportedRename} test bool tupleFieldAtUse() = {0, 1} == testRenameOccurrences(" 'tuple[int foo] t = \<8\>; 'y = t.foo; @@ -107,7 +155,14 @@ test bool tupleFieldAtUse() = {0, 1} == testRenameOccurrences(" // We would prefer an illegalRename exception here @expected{unsupportedRename} -test bool builtinField() = testRename(" +test bool builtinFieldSimpleType() = testRename(" 'loc l = |unknown:///|; 'f = l.top; ", oldName = "top", newName = "summit"); +// We would prefer an illegalRename exception here + +@expected{unsupportedRename} +test bool builtinFieldCollectionType() = testRename(" + 'loc l = |unknown:///|; + 'f = l.ls; +", oldName = "ls", newName = "contents"); From 26ce478560f0f53ac3b7bb116ff250287e2de9cc Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 26 Jul 2024 13:17:25 +0200 Subject: [PATCH 042/105] Implement module renaming. --- .../lang/rascal/lsp/refactor/Rename.rsc | 25 +++-- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 9 +- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 67 +++++++++++-- .../lang/rascal/tests/rename/Modules.rsc | 96 +++++++++++++++++++ .../lang/rascal/tests/rename/TestUtils.rsc | 54 +++++++---- 5 files changed, 217 insertions(+), 34 deletions(-) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index b9fbca2a4..4194a4a76 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -169,6 +169,7 @@ Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is || d is dataAbstract || d is \data; Maybe[loc] locationOfName(TypeVar tv) = just(tv.name.src); +Maybe[loc] locationOfName(Header h) = locationOfName(h.name); default Maybe[loc] locationOfName(Tree t) = nothing(); private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[loc] defs, set[loc] uses, str name) { @@ -224,6 +225,7 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s , , , + , } }; @@ -234,19 +236,28 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s loc c = min(locsContainingCursor.l); Cursor cur = cursor(use(), |unknown:///|, ""); switch (locsContainingCursor[c]) { + case {moduleName(), *_}: { + cur = cursor(moduleName(), c, cursorName); + } case {def(), *_}: { // Cursor is at a definition cur = cursor(def(), c, cursorName); } case {use(), *_}: { - if (size(getDefs(ws, c) & ws.defines.defined) > 0) { + if (d <- ws.useDef[c], just(amodule(_)) := getFact(ws, d)) { + // Cursor is at an import + cur = cursor(moduleName(), c, cursorName); + } else if (u <- ws.useDef<0>, u.begin <= cursorLoc.begin && u.end > cursorLoc.end) { + // Cursor is at a qualified name + cur = cursor(moduleName(), c, cursorName); + } else if (size(getDefs(ws, c) & ws.defines.defined) > 0) { // The cursor is at a use with corresponding definitions. cur = cursor(use(), c, cursorName); - } else if (ws.facts[c]?) { - if (aparameter(cursorName, _) := ws.facts[c]) { + } else if (just(at) := getFact(ws, c)) { + if (aparameter(cursorName, _) := at) { // The cursor is at a type parameter cur = cursor(typeParam(), c, cursorName); - } else if (ws.facts[c].alabel == cursorName) { + } else if (at.alabel == cursorName) { // The cursor is at a collection field cur = cursor(collectionField(), c, cursorName); } @@ -264,7 +275,7 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); step("collecting uses of \'\'", 1); - = getDefsUses(ws, cur, rascalMayOverloadSameName); + = getDefsUses(ws, cur, rascalMayOverloadSameName); rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; @@ -280,9 +291,7 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s } list[DocumentEdit] changes = [changed(file, moduleResults[file].edits) | file <- moduleResults]; - - // TODO If the cursor was a module name, we need to rename files as well - list[DocumentEdit] renames = []; + list[DocumentEdit] renames = [renamed(from, to) | <- getRenames(newName)]; return changes + renames; }, totalWork = 4); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 53fa8ab9d..b16f5ccee 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -30,6 +30,7 @@ import IO; import List; import Location; import Message; +import String; import util::Maybe; import util::Reflective; @@ -51,11 +52,18 @@ loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { [end = ]; } +bool isPrefixOf(loc prefix, loc l) = l.scheme == prefix.scheme + && l.authority == prefix.authority + && startsWith(l.path, prefix.path); + start[Module] parseModuleWithSpacesCached(loc l) { @memo{expireAfter(minutes=5)} start[Module] parseModuleWithSpacesCached(loc l, datetime _) = parseModuleWithSpaces(l); return parseModuleWithSpacesCached(l, lastModified(l)); } +Maybe[&B] flatMap(nothing(), Maybe[&B](&A) _) = nothing(); +Maybe[&B] flatMap(just(&A a), Maybe[&B](&A) f) = f(a); + str toString(error(msg, l)) = "[error] \'\' at "; str toString(error(msg)) = "[error] \'\'"; str toString(warning(msg, l)) = "[warning] \'\' at "; @@ -69,4 +77,3 @@ str toString(map[str, list[Message]] moduleMsgs) = rel[&K, &V] groupBy(set[&V] s, &K(&V) pred) = { | v <- s}; - diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 4b02beffc..0d3420d70 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -57,6 +57,7 @@ data CursorKind = use() | def() | typeParam() | collectionField() + | moduleName() ; data Cursor = cursor(CursorKind kind, loc l, str name); @@ -68,7 +69,8 @@ data WorkspaceInfo ( set[Define] defines = {}, set[loc] modules = {}, map[loc, Define] definitions = (), - map[loc, AType] facts = () + map[loc, AType] facts = (), + map[loc, str] moduleNames = () ) = workspaceInfo(set[loc] folders); private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { @@ -76,6 +78,7 @@ private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { ws.defines += tm.defines; ws.definitions += tm.definitions; ws.facts += tm.facts; + ws.moduleNames += (tm.moduleLocs[n].top: n | n <- tm.moduleLocs); return ws; } @@ -120,6 +123,8 @@ set[loc] getUses(WorkspaceInfo ws, set[loc] defs) = invert(ws.useDef)[defs]; set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; +Maybe[AType] getFact(WorkspaceInfo ws, loc l) = l in ws.facts ? just(ws.facts[l]) : nothing(); + set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { set[loc] overloadedLocs = defs; @@ -136,19 +141,22 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv return overloadedLocs; } -tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(use(), l, _), MayOverloadFun mayOverloadF) { +private rel[loc, loc] NO_RENAMES(str _) = {}; +private int qualSepSize = size("::"); + +tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF) { defs = getOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); uses = getUses(ws, defs); - return ; + return ; } -tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(def(), l, _), MayOverloadFun mayOverloadF) { +tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(def(), l, _), MayOverloadFun mayOverloadF) { defs = getOverloadedDefs(ws, {l}, mayOverloadF); uses = getUses(ws, defs); - return ; + return ; } -tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _) { +tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _) { AType at = ws.facts[cursorLoc]; if(Define d: <_, _, _, _, _, defType(afunc(_, /at, _))> <- ws.defines, isContainedIn(cursorLoc, d.defined)) { // From here on, we can assume that all locations are in the same file, because we are dealing with type parameters and filtered on `isContainedIn` @@ -172,13 +180,13 @@ tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), curs , !any(flInner <- facts<0>, isStrictlyContainedIn(flInner, l)) // Filter out any facts that contain other facts }; - return ; + return ; } throw unsupportedRename("Cannot find function definition which defines template variable \'\'"); } -tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _) { +tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _) { AType cursorType = ws.facts[cursorLoc]; println("Cursor type: "); factLocsSortedBySize = sort(domain(ws.facts), bool(loc l1, loc l2) { return l1.length < l2.length; }); @@ -249,6 +257,47 @@ tuple[set[loc], set[loc]] getDefsUses(WorkspaceInfo ws, cursor(collectionField() throw unsupportedRename("Cannot rename field that is not declared inside this workspace."); } - return ; + return ; } +tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _) { + loc moduleFile = |unknown:///|; + if (d <- ws.useDef[cursorLoc], amodule(_) := ws.facts[d]) { + // Cursor is at an import + moduleFile = d.top; + } else if (just(l) := findSmallestContaining(ws.facts<0>, cursorLoc), amodule(_) := ws.facts[l]) { + // Cursor is at a module header + moduleFile = l.top; + } else { + // Cursor is at the module part of a qualified use + if ( <- ws.useDef, u.begin <= cursorLoc.begin, u.end == cursorLoc.end) { + moduleFile = d.top; + } else { + throw unsupportedRename("Could not find cursor location in TPL."); + } + } + + modName = ws.moduleNames[moduleFile]; + + defs = {parseModuleWithSpacesCached(moduleFile).top.header.name.names[-1].src}; + + imports = {u | u <- ws.useDef<0>, amodule(modName) := ws.facts[u]}; + qualifiedUses = { + // We compute the location of the module name in the qualified name at `u` + // some::qualified::path::to::Foo::SomeVar + // \____________________________/\/\_____/ + // moduleNameSize ^ qualSepSize ^ ^ idSize + trim(u, removePrefix = moduleNameSize - size(cursorName) + , removeSuffix = idSize + qualSepSize) + | <- ws.useDef o toRel(ws.definitions) + , idSize := size(d.id) + , u.length > idSize // There might be a qualified prefix + , moduleNameSize := size(modName) + , u.length == moduleNameSize + qualSepSize + idSize + }; + uses = imports + qualifiedUses; + + rel[loc, loc] getRenames(str newName) = { | d <- defs, file := d.top}; + + return ; +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc new file mode 100644 index 000000000..565442a37 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc @@ -0,0 +1,96 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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 lang::rascal::tests::rename::Modules + +import lang::rascal::tests::rename::TestUtils; +// import lang::rascal::lsp::refactor::Exception; + +test bool singleModuleFromHeader() = testRenameOccurrences({ + byText("Foo", " + 'data Bool = t() | f(); + 'Bool and(Bool l, Bool r) = r is t ? l : f; + ", {0}, newName = "Bar"), + byText("Main", " + 'import Foo; + 'void main() { + ' Foo::Bool b = and(t(), f()); + '} + ", {0, 1}) +}, <"Foo", "Foo", 0>, newName = "Bar"); + +test bool deepModuleFromHeader() = testRenameOccurrences({ + byText("some::path::to::Foo", " + 'data Bool = t() | f(); + 'Bool and(Bool l, Bool r) = r is t ? l : f; + ", {0}, newName = "some::path::to::Bar"), + byText("Main", " + 'import some::path::to::Foo; + 'void main() { + ' some::path::to::Foo::Bool b = and(t(), f()); + '} + ", {0, 1}) +}, <"some::path::to::Foo", "Foo", 0>, newName = "Bar"); + +test bool shadowedModuleFromHeader() = testRenameOccurrences({ + byText("Foo", " + 'data Bool = t() | f(); + 'Bool and(Bool l, Bool r) = r is t ? l : f; + ", {0}, newName = "Bar"), + byText("shadow::Foo", "", {}), + byText("Main", " + 'import Foo; + 'void main() { + ' Foo::Bool b = and(t(), f()); + '} + ", {0, 1}) +}, <"Foo", "Foo", 0>, newName = "Bar"); + +test bool singleModuleFromImport() = testRenameOccurrences({ + byText("Foo", " + 'data Bool = t() | f(); + 'Bool and(Bool l, Bool r) = r is t ? l : f; + ", {0}, newName = "Bar"), + byText("Main", " + 'import Foo; + 'void main() { + ' Foo::Bool b = and(t(), f()); + '} + ", {0, 1}) +}, <"Main", "Foo", 0>, newName = "Bar"); + +test bool singleModuleFromReference() = testRenameOccurrences({ + byText("util::Foo", " + 'data Bool = t() | f(); + 'Bool and(Bool l, Bool r) = r is t ? l : f; + ", {0}, newName = "util::Bar"), + byText("Main", " + 'import util::Foo; + 'void main() { + ' util::Foo::Bool b = and(t(), f()); + '} + ", {0, 1}) +}, <"Main", "Foo", 1>, newName = "Bar"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index ee3206b96..6b32f050c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -67,24 +67,43 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); - editsPerModule = (); - for (changed(f, textedits) <- edits) { - start[Module] m = parseModuleWithSpaces(f); - str name = getModuleName(f, pcfg); - set[loc] locs = {l | replace(l, _) <- textedits}; - set[int] occs = locsToOccs(m, cursor.id, locs); - - str newName = name; - if (renamed(f, newLoc) <- edits) { - newName = getModuleName(newLoc, pcfg); - } - - editsPerModule[name] = ; - } + renamesPerModule = ( + beforeRename: afterRename + | renamed(oldLoc, newLoc) <- edits + , beforeRename := getModuleName(oldLoc, pcfg) + , afterRename := getModuleName(newLoc, pcfg) + ); + + replacesPerModule = ( + name: occs + | changed(file, changes) <- edits + , name := getModuleName(file, pcfg) + , locs := {l | replace(l, _) <- changes} + , occs := locsToOccs(parseModuleWithSpaces(file), cursor.id, locs) + ); + + editsPerModule = ( + name : + | srcDir <- pcfg.srcs + , file <- find(srcDir, "rsc") + , name := getModuleName(file, pcfg) + , occs := replacesPerModule[name] ? {} + , nameAfterRename := renamesPerModule[name] ? name + ); expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); - return editsPerModule == expectedEditsPerModule; + if (editsPerModule != expectedEditsPerModule) { + print("EXPECTED: "); + iprintln(expectedEditsPerModule); + println(); + + print("ACTUAL: "); + iprintln(editsPerModule); + println(); + return false; + } + return true; } set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { @@ -173,7 +192,10 @@ private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] private str moduleNameToPath(str name) = replaceAll(name, "::", "/"); private str modulePathToName(str path) = replaceAll(path, "/", "::"); -private Tree findCursor(loc f, str id, int occ) = [n | m := parseModuleWithSpaces(f), /Name n := m.top, "" == id][occ]; +private Tree findCursor(loc f, str id, int occ) { + m = parseModuleWithSpaces(f); + return [n | /Name n := m.top, "" == id][occ]; +} private loc storeTestModule(loc dir, str name, str body) { str moduleStr = " From 4bdeb786026cf4a4db32a9f57232ae50df44fcf3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 26 Jul 2024 13:18:32 +0200 Subject: [PATCH 043/105] Fix warnings. --- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 2 +- .../lang/rascal/tests/rename/TestUtils.rsc | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index b16f5ccee..961fec903 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -70,7 +70,7 @@ str toString(warning(msg, l)) = "[warning] \'\' at "; str toString(info(msg, l)) = "[info] \'\' at "; str toString(list[Message] msgs, int indent = 1) = - intercalate("\n", ([] | it + " <}>- " | msg <- msgs)); + intercalate("\n", ([] | it + " <}>- " | msg <- msgs)); str toString(map[str, list[Message]] moduleMsgs) = intercalate("\n", ([] | it + "Messages for :\n" | m <- moduleMsgs)); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 6b32f050c..6aeefca15 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -132,16 +132,16 @@ private PathConfig getTestPathConfig(loc testDir) { ); } -private PathConfig getTestWorkspaceConfig() { - return pathConfig( - bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, - libs=[|lib://rascal|], - srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal| - , |project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal|], - resources=|memory://tests/rename/resources|, - generatedSources=|memory://tests/rename/generated-sources| - ); -} +// private PathConfig getTestWorkspaceConfig() { +// return pathConfig( +// bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, +// libs=[|lib://rascal|], +// srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal| +// , |project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal|], +// resources=testDir + "resources", +// generatedSources=testDir + "generated-sources" +// ); +// } list[DocumentEdit] getEdits(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { loc f = resolveLocation(singleModule); From dd1dceec466dba588e92b8b4b336eb34191dae15 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 26 Jul 2024 13:18:49 +0200 Subject: [PATCH 044/105] Don't load new parser for tests. --- .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 1 + 1 file changed, 1 insertion(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 6aeefca15..b03d5ba8c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -24,6 +24,7 @@ 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 lang::rascal::tests::rename::TestUtils import lang::rascal::lsp::refactor::Rename; // Module under test From 3999de82be3008be75f9f0537b2d2be44fb98fa6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 11:41:16 +0200 Subject: [PATCH 045/105] Verify type-correctness of renaming in tests. --- .../lang/rascal/lsp/refactor/Exception.rsc | 18 +++++ .../lang/rascal/lsp/refactor/Rename.rsc | 14 +++- .../lang/rascal/tests/rename/TestUtils.rsc | 71 +++++++++++++------ 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index aa961980a..08a76a437 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -26,6 +26,11 @@ POSSIBILITY OF SUCH DAMAGE. } module lang::rascal::lsp::refactor::Exception +import analysis::typepal::TModel; + +import Message; +import Set; + data Cursor; alias Capture = tuple[loc def, loc use]; @@ -42,3 +47,16 @@ data RenameException | unsupportedRename(str message, rel[loc location, str message] issues = {}) | unexpectedFailure(str message) ; + +void throwAnyErrors(TModel tm) { + throwAnyErrors(tm.messages); +} + +void throwAnyErrors(set[Message] msgs) { + throwAnyErrors(toList(msgs)); +} + +void throwAnyErrors(list[Message] msgs) { + errors = {msg | msg <- msgs, msg is error}; + if (errors != {}) throw errors; +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 4194a4a76..fb530af83 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -46,12 +46,14 @@ import String; import lang::rascal::\syntax::Rascal; +import lang::rascalcore::check::Checker; import lang::rascalcore::check::Import; import lang::rascalcore::check::RascalConfig; import analysis::typepal::TypePal; +import analysis::typepal::Collector; -import lang::rascal::lsp::refactor::Exception; +extend lang::rascal::lsp::refactor::Exception; import lang::rascal::lsp::refactor::Util; import lang::rascal::lsp::refactor::WorkspaceInfo; @@ -63,6 +65,16 @@ import util::Maybe; import util::Monitor; import util::Reflective; +void throwAnyErrors(list[ModuleMessages] mmsgs) { + for (mmsg <- mmsgs) { + throwAnyErrors(mmsg); + } +} + +void throwAnyErrors(program(_, msgs)) { + throwAnyErrors(msgs); +} + set[IllegalRenameReason] checkLegalName(str name) { try { parse(#Name, escapeName(name)); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index b03d5ba8c..8ecd22078 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -37,10 +37,12 @@ import String; import lang::rascal::\syntax::Rascal; // `Name` +import lang::rascalcore::check::Checker; +import lang::rascalcore::check::BasicRascalConfig; import lang::rascalcore::check::RascalConfig; import lang::rascalcore::compile::util::Names; -import analysis::diff::edits::TextEdits; +import analysis::diff::edits::ExecuteTextEdits; import util::FileSystem; import util::Math; @@ -51,6 +53,34 @@ import util::Reflective; data TestModule = byText(str name, str body, set[int] expectedRenameOccs, str newName = name) | byLoc(loc file, set[int] expectedRenameOccs, str newName = name); +private list[DocumentEdit] sortEdits(list[DocumentEdit] edits) = [sortChanges(e) | e <- edits]; + +private DocumentEdit sortChanges(changed(loc l, list[TextEdit] edits)) = changed(l, sort(edits, bool(TextEdit e1, TextEdit e2) { + return e1.range.offset < e2.range.offset; +})); +private default DocumentEdit sortChanges(DocumentEdit e) = e; + +private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathConfig pcfg) { + executeDocumentEdits(sortEdits(edits)); + remove(pcfg.resources); + RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true][verbose = false][logPathConfig = false]; + throwAnyErrors(checkAll(root, ccfg)); +} + +bool ASSERT_EQ(&T expected, &T actual) { + if (expected != actual) { + print("EXPECTED: "); + iprintln(expected); + println(); + + print("ACTUAL: "); + iprintln(actual); + println(); + return false; + } + return true; +} + bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { str testName = "Test"; loc testDir = |memory://tests/rename/|; @@ -94,28 +124,21 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); - if (editsPerModule != expectedEditsPerModule) { - print("EXPECTED: "); - iprintln(expectedEditsPerModule); - println(); - - print("ACTUAL: "); - iprintln(editsPerModule); - println(); - return false; + for (src <- pcfg.srcs) { + verifyTypeCorrectRenaming(src, edits, pcfg); } - return true; + + return ASSERT_EQ(editsPerModule, expectedEditsPerModule); } set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); - occs = extractRenameOccurrences(moduleFileName, edits, oldName); + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); return occs; } // Test renames that are expected to throw an exception bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { - edits = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); + = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); print("UNEXPECTED EDITS: "); iprintln(edits); @@ -144,20 +167,28 @@ private PathConfig getTestPathConfig(loc testDir) { // ); // } -list[DocumentEdit] getEdits(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { +tuple[list[DocumentEdit], set[int]] getEdits(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { loc f = resolveLocation(singleModule); m = parseModuleWithSpaces(f); Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(cursor, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); + edits = renameRascalSymbol(cursor, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); + + occs = extractRenameOccurrences(singleModule, edits, oldName); + + for (src <- pcfg.srcs) { + verifyTypeCorrectRenaming(src, edits, pcfg); + } + + return ; } list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports) { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); return edits; } -private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule") { +private tuple[list[DocumentEdit], set[int], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule") { str moduleStr = "module ' @@ -171,8 +202,8 @@ private tuple[list[DocumentEdit], loc] getEditsAndModule(str stmtsStr, int curso loc moduleFileName = testDir + "rascal" + ".rsc"; writeFile(moduleFileName, moduleStr); - edits = getEdits(moduleFileName, testDir, cursorAtOldNameOccurrence, oldName, newName); - return ; + = getEdits(moduleFileName, testDir, cursorAtOldNameOccurrence, oldName, newName); + return ; } private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { From ef1d7d231ab99f9bdd2be590ab2ec499d105abdc Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 12:34:02 +0200 Subject: [PATCH 046/105] Fix duplicate field declarations. --- .../src/main/rascal/lang/rascal/tests/rename/Fields.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 2bbd58c72..01b20eef9 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -32,13 +32,13 @@ import lang::rascal::lsp::refactor::Exception; test bool dataFieldAtDef() = {0, 1} == testRenameOccurrences(" 'D oneTwo = d(1, 2); 'x = d.foo; - ", decls = "data D = d(int foo, int bar);" + ", decls = "data D = d(int foo, int baz);" ); test bool dataFieldAtUse() = {0, 1} == testRenameOccurrences(" 'D oneTwo = d(1, 2); 'x = d.foo; - ", decls = "data D = d(int foo, int bar);" + ", decls = "data D = d(int foo, int baz);" , cursorAtOldNameOccurrence = 1); test bool duplicateDataField() = {0, 1, 2} == testRenameOccurrences(" From ee30bdd7b010c742e1acc610c2a0e1996098f559 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 12:54:48 +0200 Subject: [PATCH 047/105] Implement check for duplicate field names. --- .../main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 13 ++++++++++++- .../main/rascal/lang/rascal/tests/rename/Fields.rsc | 9 +++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index fb530af83..5747fad31 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -95,7 +95,18 @@ private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, , !rascalMayOverload({cD, nD.defined}, ws.definitions) }; - return {doubleDeclaration(old, doubleDeclarations[old]) | old <- doubleDeclarations.old}; + rel[loc old, loc new] doubleFieldDeclarations = { + | Define _: <- newDefs + , loc cD <- currentDefs + , ws.definitions[cD]? + , Define _: := ws.definitions[cD] + , fL <- ws.facts, at := ws.facts[fL] + , acons(aadt(_, _, _), _, _) := at + , isStrictlyContainedIn(cD, fL) + , isStrictlyContainedIn(nD, fL) + }; + + return {doubleDeclaration(old, doubleDeclarations[old]) | old <- (doubleDeclarations + doubleFieldDeclarations).old}; } private set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 01b20eef9..88f871e00 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -41,12 +41,17 @@ test bool dataFieldAtUse() = {0, 1} == testRenameOccurrences(" ", decls = "data D = d(int foo, int baz);" , cursorAtOldNameOccurrence = 1); -test bool duplicateDataField() = {0, 1, 2} == testRenameOccurrences(" +test bool multipleConstructorDataField() = {0, 1, 2} == testRenameOccurrences(" 'x = d(1, 2); 'y = x.foo; - ", decls = "data D = d(int foo) | d(int foo, int bar);" + ", decls = "data D = d(int foo) | d(int foo, int baz);" , cursorAtOldNameOccurrence = 1); +@expected{illegalRename} +test bool duplicateDataField() = testRename("", decls = + "data D = d(int foo, int bar);" +); + test bool crossModuleDataFieldAtDef() = testRenameOccurrences({ byText("Foo", "data D = a(int foo) | b(int bar);", {0}), byText("Main", " From cbb72292c553649c538edf072055a78d3ba4423f Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 17:33:24 +0200 Subject: [PATCH 048/105] Fix isPrefixOf when trailing slash is missing. --- rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 961fec903..9ec081310 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -54,7 +54,7 @@ loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { bool isPrefixOf(loc prefix, loc l) = l.scheme == prefix.scheme && l.authority == prefix.authority - && startsWith(l.path, prefix.path); + && startsWith(l.path, endsWith(prefix.path, "/") ? prefix.path : prefix.path + "/"); start[Module] parseModuleWithSpacesCached(loc l) { @memo{expireAfter(minutes=5)} start[Module] parseModuleWithSpacesCached(loc l, datetime _) = parseModuleWithSpaces(l); From a3363fdd5019b859bd702e03c981d402f492b605 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 17:35:01 +0200 Subject: [PATCH 049/105] Use alias instead of complex tuple. --- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 0d3420d70..1b4f29851 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -63,6 +63,8 @@ data CursorKind = use() data Cursor = cursor(CursorKind kind, loc l, str name); alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines); +alias FileRenamesF = rel[loc old, loc new](str newName); +alias DefsUsesRenames = tuple[set[loc] defs, set[loc] uses, FileRenamesF renames]; data WorkspaceInfo ( rel[loc use, loc def] useDef = {}, @@ -144,19 +146,19 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv private rel[loc, loc] NO_RENAMES(str _) = {}; private int qualSepSize = size("::"); -tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF) { +DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { defs = getOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); uses = getUses(ws, defs); return ; } -tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(def(), l, _), MayOverloadFun mayOverloadF) { +DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(def(), l, _), MayOverloadFun mayOverloadF, PathConfig(loc) _) { defs = getOverloadedDefs(ws, {l}, mayOverloadF); uses = getUses(ws, defs); return ; } -tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _) { +DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { AType at = ws.facts[cursorLoc]; if(Define d: <_, _, _, _, _, defType(afunc(_, /at, _))> <- ws.defines, isContainedIn(cursorLoc, d.defined)) { // From here on, we can assume that all locations are in the same file, because we are dealing with type parameters and filtered on `isContainedIn` @@ -186,7 +188,7 @@ tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, curs throw unsupportedRename("Cannot find function definition which defines template variable \'\'"); } -tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _) { +DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { AType cursorType = ws.facts[cursorLoc]; println("Cursor type: "); factLocsSortedBySize = sort(domain(ws.facts), bool(loc l1, loc l2) { return l1.length < l2.length; }); @@ -260,7 +262,7 @@ tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, curs return ; } -tuple[set[loc], set[loc], rel[loc, loc](str)] getDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _) { +DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) getPathConfig) { loc moduleFile = |unknown:///|; if (d <- ws.useDef[cursorLoc], amodule(_) := ws.facts[d]) { // Cursor is at an import From 90ca2f4b16a9554b987373e52c4168642ded4fea Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 17:36:20 +0200 Subject: [PATCH 050/105] Do not load workspace for local renaming. --- .../lang/rascal/lsp/refactor/Rename.rsc | 116 ++++++++++++++---- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 77 ++++++------ 2 files changed, 133 insertions(+), 60 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 5747fad31..c7baf412a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -61,6 +61,7 @@ import analysis::diff::edits::TextEdits; import vis::Text; +import util::FileSystem; import util::Maybe; import util::Monitor; import util::Reflective; @@ -215,29 +216,32 @@ private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitio return rascalMayOverload(defs, potentialOverloadDefinitions); } -list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { - step("collecting workspace information", 1); +private bool isFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { + for (d <- defs) { + if (Define _: <_, _, _, _, funDef, defType(afunc(_, _, _))> <- ws.defines + , isContainedIn(ws.definitions[d].scope, funDef)) { + continue; + } + return false; + } + return true; +} +private bool isFunctionLocal(WorkspaceInfo ws, cursor(def(), cursorLoc, _)) = + isFunctionLocalDefs(ws, getOverloadedDefs(ws, {cursorLoc}, rascalMayOverloadSameName)); +private bool isFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)) = + isFunctionLocalDefs(ws, getOverloadedDefs(ws, getDefs(ws, cursorLoc), rascalMayOverloadSameName)); +private bool isFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; +private bool isFunctionLocal(WorkspaceInfo _, cursor(collectionField(), _, _)) = false; +private bool isFunctionLocal(WorkspaceInfo _, cursor(moduleName(), _, _)) = false; + +tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; str cursorName = ""; - println("Cursor is at id \'\' at "); - cursorAsName = [Name] cursorName; - escapedCursorAsName = startsWith(cursorName, "\\") ? cursorName : [Name] "\\"; - - WorkspaceInfo ws = gatherWorkspaceInfo(workspaceFolders, getPathConfig, fileFilter = bool(loc l) { - // If we do not find any occurrences of the name under the cursor in a module, - // we are not interested in it at all, and will skip loading its TPL. - m = parseModuleWithSpacesCached(l); - if (/cursorAsName := m) { - return true; - } else if (escapedCursorAsName != cursorAsName, /escapedCursorAsName := m) { - return true; - } - return false; - }); + println("Cursor is at id \'\' at "); - step("analyzing name at cursor", 1); + ws = preLoad(ws); cursorNamedDefs = (ws.defines)[cursorName]; @@ -297,17 +301,85 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); + return ; +} + +private bool containsName(loc l, str name) { + // If we do not find any occurrences of the name under the cursor in a module, + // we are not interested in it at all, and will skip loading its TPL. + cursorAsName = [Name] name; + escapedCursorAsName = startsWith(name, "\\") ? name : [Name] "\\"; + + m = parseModuleWithSpacesCached(l); + if (/cursorAsName := m) { + return true; + } else if (escapedCursorAsName != cursorAsName, /escapedCursorAsName := m) { + return true; + } + return false; +} + +list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { + loc cursorLoc = cursorT.src; + str cursorName = ""; + + step("collecting workspace information", 1); + WorkspaceInfo ws = workspaceInfo( + // Preload + ProjectFiles() { + return { < + min([f | f <- workspaceFolders, isPrefixOf(f, cursorLoc)]), + cursorLoc.top + > }; + }, + // Full load + ProjectFiles() { + return { + | folder <- workspaceFolders + , PathConfig pcfg := getPathConfig(folder) + , srcFolder <- pcfg.srcs + , file <- find(srcFolder, "rsc") + , file != cursorLoc.top // because we loaded that during preload + , containsName(file, cursorName) + }; + }, + // Load TModel for loc + set[TModel](ProjectFiles projectFiles) { + set[TModel] tmodels = {}; + + if (projectFiles == {}) return tmodels; + + for (projectFolder <- projectFiles.projectFolder, \files := projectFiles[projectFolder]) { + PathConfig pcfg = getPathConfig(projectFolder); + RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true] + [verbose = false] + [logPathConfig = false]; + ms = rascalTModelForLocs(toList(\files), ccfg, dummy_compile1); + tmodels += {convertTModel2PhysicalLocs(tm) | m <- ms.tmodels, tm := ms.tmodels[m]}; + } + return tmodels; + } + ); + + step("analyzing name at cursor", 1); + = getCursor(ws, cursorT); + + step("loading required type information", 1); + if (!isFunctionLocal(ws, cur)) { + ws = loadWorkspace(ws); + } + step("collecting uses of \'\'", 1); - = getDefsUses(ws, cur, rascalMayOverloadSameName); + = getDefsUses(ws, cur, rascalMayOverloadSameName, getPathConfig); rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; - files = defsPerFile.file + usesPerFile.file; + set[loc] \files = defsPerFile.file + usesPerFile.file; step("checking rename validity", 1); map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults = - (file: | file <- files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName)); + (file: | file <- \files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName)); if (reasons := union({moduleResults[file].reasons | file <- moduleResults}), reasons != {}) { throw illegalRename(cur, reasons); @@ -317,7 +389,7 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s list[DocumentEdit] renames = [renamed(from, to) | <- getRenames(newName)]; return changes + renames; -}, totalWork = 4); +}, totalWork = 5); //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 1b4f29851..f6f35962b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -32,7 +32,6 @@ import Relation; import analysis::typepal::TModel; import lang::rascalcore::check::Checker; -import lang::rascalcore::check::RascalConfig; import lang::rascal::\syntax::Rascal; @@ -48,11 +47,9 @@ import IO; import List; import Location; import Map; -import Message; import Set; import String; - data CursorKind = use() | def() | typeParam() @@ -65,59 +62,63 @@ data Cursor = cursor(CursorKind kind, loc l, str name); alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines); alias FileRenamesF = rel[loc old, loc new](str newName); alias DefsUsesRenames = tuple[set[loc] defs, set[loc] uses, FileRenamesF renames]; +alias ProjectFiles = rel[loc projectFolder, loc file]; data WorkspaceInfo ( + // Instance fields + // Read-only rel[loc use, loc def] useDef = {}, set[Define] defines = {}, set[loc] modules = {}, map[loc, Define] definitions = (), map[loc, AType] facts = (), - map[loc, str] moduleNames = () -) = workspaceInfo(set[loc] folders); - -private WorkspaceInfo loadModel(WorkspaceInfo ws, TModel tm) { - ws.useDef += tm.useDef; - ws.defines += tm.defines; - ws.definitions += tm.definitions; - ws.facts += tm.facts; - ws.moduleNames += (tm.moduleLocs[n].top: n | n <- tm.moduleLocs); + set[loc] projects = {} +) = workspaceInfo( + ProjectFiles() preloadFiles, + ProjectFiles() allFiles, + set[TModel](ProjectFiles) tmodelsForLocs +); + +WorkspaceInfo loadLocs(WorkspaceInfo ws, ProjectFiles projectFiles) { + for (tm <- ws.tmodelsForLocs(projectFiles)) { + ws = loadTModel(ws, tm); + } + ws.modules += projectFiles.file; + ws.projects += projectFiles.projectFolder; return ws; } -private void checkNoErrors(ModuleStatus ms) { - errors = (m: msgs | m <- ms.messages - , msgs := [msg | msg <- ms.messages[m], msg is error] - , msgs != []); - if (errors != ()) - throw unsupportedRename("Cannot rename: some modules in workspace have errors.\n", issues={<(error.at ? |unknown:///|), error.msg> | m <- errors, error <- errors[m]}); +WorkspaceInfo preLoad(WorkspaceInfo ws) { + return loadLocs(ws, ws.preloadFiles()); } -WorkspaceInfo gatherWorkspaceInfo(set[loc] folders, PathConfig(loc) getPathConfig, bool(loc) fileFilter = bool(loc l) { return true; }) = job("loading workspace information", WorkspaceInfo(void(str, int) step) { - ws = workspaceInfo(folders); - - for (projectFolder <- folders) { - step("loading modules for project \'\'", 1); - PathConfig pcfg = getPathConfig(projectFolder); +WorkspaceInfo loadWorkspace(WorkspaceInfo ws) { + return loadLocs(ws, ws.allFiles()); +} - fs = [file | srcFolder <- pcfg.srcs, file <- find(srcFolder, "rsc"), fileFilter(file)]; +WorkspaceInfo loadTModel(WorkspaceInfo ws, TModel tm) { + try { + throwAnyErrors(tm); + } catch set[Message] errors: { + throw unsupportedRename("Cannot rename: some modules in workspace have errors.\n", issues={<(error.at ? |unknown:///|), error.msg> | error <- errors}); + } - if (fs == []) - continue; + ws.useDef += tm.useDef; + ws.defines += tm.defines; + ws.definitions += tm.definitions; + ws.facts += tm.facts; - RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true][verbose = false][logPathConfig = false]; - ms = rascalTModelForLocs(fs, ccfg, dummy_compile1); - checkNoErrors(ms); + return ws; +} - for (m <- ms.tmodels) { - tm = convertTModel2PhysicalLocs(ms.tmodels[m]); - ws = loadModel(ws, tm); - ws.modules += {ms.moduleLocs[m].top}; - } +loc getProjectFolder(WorkspaceInfo ws, loc l) { + if (project <- ws.projects, isPrefixOf(project, l)) { + return project; } - return ws; -}, totalWork = size(folders)); + throw "Could not find project containing "; +} set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; @@ -279,7 +280,7 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cu } } - modName = ws.moduleNames[moduleFile]; + modName = getModuleName(moduleFile, getPathConfig(getProjectFolder(ws, moduleFile))); defs = {parseModuleWithSpacesCached(moduleFile).top.header.name.names[-1].src}; From 6d1bd05b30c37d97474f46dc67e8efe988b34cde Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 30 Jul 2024 17:37:55 +0200 Subject: [PATCH 051/105] Add benchmarking code. --- .../lang/rascal/lsp/refactor/Benchmark.rsc | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc new file mode 100644 index 000000000..a4ab161f2 --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc @@ -0,0 +1,83 @@ +@license{ +Copyright (c) 2018-2023, NWO-I CWI and 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. +} +@bootstrapParser + +module lang::rascal::lsp::refactor::Benchmark + +import IO; +import Set; +import lang::rascal::\syntax::Rascal; + +import util::Benchmark; +import util::FileSystem; +import util::Reflective; + +void main() { + name = "val"; + + println("Collecting files..."); + fs = find(|home:///swat/projects/Rascal/rascal/src|, "rsc"); + + println("Parsing modules..."); + trees = {parseModuleWithSpaces(f) | f <- fs}; + + println("Benchmarking trees"); + iprintln(benchmark(( + "Deep match (once)": void() { deepMatchOnce(trees, name); }, + "Deep match (twice)": void() { deepMatchTwice(trees, name); } + ))); +} + +void deepMatchTwice(set[start[Module]] trees, str name) { + int matches = 0; + + reg = [Name] name; + esc = [Name] "\\"; + + for (tree <- trees) { + if (/reg := tree) { + matches += 1; + } + else if (/esc := tree) { + matches += 1; + } + } + println("[Twice] # of matches: "); +} +void deepMatchOnce(set[start[Module]] trees, str name) { + int matches = 0; + + reg = [Name] name; + esc = [Name] "\\"; + + for (tree <- trees) { + if (/Name n := tree, n := reg || n := esc) { + matches += 1; + } + } + println("[Once] # of matches: "); +} From 3084b83343b6c903a2f313de814f0f267b9995eb Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 31 Jul 2024 13:30:50 +0200 Subject: [PATCH 052/105] Improve illegal rename error with message. --- .../src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc | 7 ++++++- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index 08a76a437..b578b117d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -43,11 +43,16 @@ data IllegalRenameReason ; data RenameException - = illegalRename(Cursor cursor, set[IllegalRenameReason] reason) + = illegalRename(str message, set[IllegalRenameReason] reason) | unsupportedRename(str message, rel[loc location, str message] issues = {}) | unexpectedFailure(str message) ; +str describe(invalidName(name)) = "\'\' is not a valid identifier"; +str describe(doubleDeclaration(_, _)) = "it causes double declarations"; +str describe(captureChange(_)) = "it changes program semantics"; +str describe(definitionsOutsideWorkspace(_)) = "it renames definitions outside of currently open projects"; + void throwAnyErrors(TModel tm) { throwAnyErrors(tm.messages); } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index c7baf412a..e0710709b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -382,7 +382,8 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s (file: | file <- \files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName)); if (reasons := union({moduleResults[file].reasons | file <- moduleResults}), reasons != {}) { - throw illegalRename(cur, reasons); + list[str] reasonDescs = toList({describe(r) | r <- reasons}); + throw illegalRename("Rename is not valid, because:\n - ", reasons); } list[DocumentEdit] changes = [changed(file, moduleResults[file].edits) | file <- moduleResults]; From a96b007abfe257a01dcd5a2ed5ac84f1438dbfb8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 31 Jul 2024 13:31:10 +0200 Subject: [PATCH 053/105] Document dependency override in POM. --- rascal-lsp/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml index d7636fcc5..dfe3da7d6 100644 --- a/rascal-lsp/pom.xml +++ b/rascal-lsp/pom.xml @@ -151,6 +151,7 @@ maven-surefire-plugin ${maven-surefire-plugin.version} + org.apache.maven.surefire surefire-junit47 From 1afa2fee1e0fe1592016c0cc890c58d9cb21d0fe Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 31 Jul 2024 14:11:28 +0200 Subject: [PATCH 054/105] Improve performance of comprehensions and reductions. --- rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 3 ++- rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc | 3 --- .../src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index e0710709b..3e71d152f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -135,12 +135,13 @@ private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Mod }; // Will this rename hide a used definition of `newName` behind a definition of `oldName` (shadowing)? + iUseDef = invert(ws.useDef); set[Capture] newUseShadowedByRename = { | Define nD <- newDefs - , nU <- invert(ws.useDef)[newDefs.defined] , loc cD <- currentDefs , loc cS := ws.definitions[cD].scope , isContainedIn(cS, nD.scope) + , nU <- iUseDef[newDefs.defined] , isContainedIn(nU, cS) }; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 9ec081310..7ee6e6803 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -40,9 +40,6 @@ import lang::rascal::\syntax::Rascal; Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) = (nothing() | (it == nothing() || (just(itt) := it && w < itt)) && isContainedIn(l, w) ? just(w) : it | w <- wrappers); -loc min(list[loc] locs) = - (getFirstFrom(locs) | l < l ? l : it | l <- locs); - loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { assert l.begin.line == l.end.line : "Cannot trim a multi-line location"; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index f6f35962b..e04ac1ba8 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -174,7 +174,7 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cur nextOffsets = toMapUnique(zip2(prefix(offsets), tail(offsets))); loc sentinel = |unknown:///|(0, 0, <0, 0>, <0, 0>); - defs = {(sentinel | (it == sentinel || f.length < it.length) && f.offset == nextOffset ? f : it | f <- facts<0>) | formal <- formals, nextOffsets[formal.offset]?, nextOffset := nextOffsets[formal.offset]}; + defs = {min([f | f <- facts<0>, f.offset == nextOffset]) | formal <- formals, nextOffsets[formal.offset]?, nextOffset := nextOffsets[formal.offset]}; useDefs = {trim(l, removePrefix = l.length - size(cursorName)) | l <- facts<0> From 6d4a1ac81fdfe56517026451cf46057e4c5ed56b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 31 Jul 2024 17:01:50 +0200 Subject: [PATCH 055/105] Clean up and document cursor determination. --- .../rascal/lang/rascal/lsp/refactor/Rename.rsc | 15 +++++++++------ .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 3e71d152f..22eaa1f47 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -240,19 +240,22 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; str cursorName = ""; - println("Cursor is at id \'\' at "); - ws = preLoad(ws); - cursorNamedDefs = (ws.defines)[cursorName]; - rel[loc l, CursorKind kind] locsContainingCursor = { | <- { + // Uses , cursorLoc), use()> - , + // Defs with an identifier equals the name under the cursor + , )[cursorName], cursorLoc), def()> + // Type parameters , - , + // Collection field definitions; any location where the label equals the name under the cursor + , + // Collection field uses; any location which is of set or list type, where the label of the collection element equals the name under the cursor + , + // Module name declaration, where the cursor location is in the module header , } }; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index e04ac1ba8..3cdf10de5 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -191,7 +191,6 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cur DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { AType cursorType = ws.facts[cursorLoc]; - println("Cursor type: "); factLocsSortedBySize = sort(domain(ws.facts), bool(loc l1, loc l2) { return l1.length < l2.length; }); AType fieldType = avoid(); From 77598238f7db9ba55a0757ee74caaf086a3e1f9d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 31 Jul 2024 17:02:56 +0200 Subject: [PATCH 056/105] Improve testing with actual workspaces. --- .../lang/rascal/tests/rename/TestUtils.rsc | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 8ecd22078..590e70c34 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -151,29 +151,66 @@ private PathConfig getTestPathConfig(loc testDir) { bin=testDir + "bin", libs=[|lib://rascal|], srcs=[testDir + "rascal"], - resources=testDir + "resources", + resources=testDir + "bin", generatedSources=testDir + "generated-sources" ); } -// private PathConfig getTestWorkspaceConfig() { -// return pathConfig( -// bin=|project://rascal-vscode-extension/test-workspace/test-project/target|, -// libs=[|lib://rascal|], -// srcs=[|project://rascal-vscode-extension/test-workspace/test-project/src/main/rascal| -// , |project://rascal-vscode-extension/test-workspace/test-lib/src/main/rascal|], -// resources=testDir + "resources", -// generatedSources=testDir + "generated-sources" -// ); -// } +PathConfig getRascalCorePathConfig(loc rascalCoreProject, loc typepalProject) { + return pathConfig( + srcs = [ + |std:///|, + rascalCoreProject + "src/org/rascalmpl/core/library", + typepalProject + "src" + ], + bin = rascalCoreProject + "target/test-classes", + generatedSources = rascalCoreProject + "target/generated-test-sources", + resources = rascalCoreProject + "target/generated-test-resources", + libs = [] + ); +} + +PathConfig resolveLocations(PathConfig pcfg) { + visit(pcfg) { + case loc l: resolveLocation(l); + } -tuple[list[DocumentEdit], set[int]] getEdits(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { + return pcfg; +} + +PathConfig getPathConfig(loc project) { + println("Getting path config for ()"); + + if (project.file == "rascal-core") { + pcfg = getRascalCorePathConfig(); + return resolveLocations(pcfg); + } + + pcfg = getProjectPathConfig(project); + return resolveLocations(pcfg); +} + +list[DocumentEdit] testRascalCore(loc rascalCoreDir, loc typepalDir) { + registerLocations("project", "", ( + |project://rascal-core/target/test-classes|: rascalCoreDir + "target/test-classes", + |project://rascal-core/target/generated-test-sources|: rascalCoreDir + "target/generated-test-sources", + |project://rascal-core/target/generated-test-resources|: rascalCoreDir + "target/generated-test-resources", + |project://rascal-core/src/org/rascalmpl/core/library|: rascalCoreDir + "src/org/rascalmpl/core/library", + |project://typepal/src|: typepalDir + "src")); + + return getEdits(rascalCoreDir + "src/org/rascalmpl/core/library/lang/rascalcore/check/ATypeBase.rsc", {resolveLocation(rascalCoreDir), resolveLocation(typepalDir)}, 0, "arat", "arational", getPathConfig); +} + +list[DocumentEdit] getEdits(loc singleModule, set[loc] projectDirs, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig(loc) getPathConfig) { loc f = resolveLocation(singleModule); m = parseModuleWithSpaces(f); Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - edits = renameRascalSymbol(cursor, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); + return renameRascalSymbol(cursor, projectDirs, newName, getPathConfig); +} +tuple[list[DocumentEdit], set[int]] getEditsAndOccurrences(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { + edits = getEdits(singleModule, {projectDir}, cursorAtOldNameOccurrence, oldName, newName, PathConfig(loc _) { return pcfg; }); occs = extractRenameOccurrences(singleModule, edits, oldName); for (src <- pcfg.srcs) { @@ -202,7 +239,7 @@ private tuple[list[DocumentEdit], set[int], loc] getEditsAndModule(str stmtsStr, loc moduleFileName = testDir + "rascal" + ".rsc"; writeFile(moduleFileName, moduleStr); - = getEdits(moduleFileName, testDir, cursorAtOldNameOccurrence, oldName, newName); + = getEditsAndOccurrences(moduleFileName, testDir, cursorAtOldNameOccurrence, oldName, newName); return ; } @@ -217,6 +254,8 @@ private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] idx = {indexOf(oldNameOccurrences, l) | replace(l, _) <- replaces}; return idx; } else { + print("Unexpected changes: "); + iprintln(edits); throw "Unexpected changes: "; } } From 9ecc1a6ab4b6ed67132903e94ee9c883994e7138 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 1 Aug 2024 15:04:50 +0200 Subject: [PATCH 057/105] Small stylistic fixes from review (@DavyLandman) --- .../lang/rascal/lsp/refactor/Rename.rsc | 10 ++++------ .../rascal/lang/rascal/lsp/refactor/Util.rsc | 12 ++++++++++-- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 19 +++++++++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 22eaa1f47..168da147a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -90,8 +90,7 @@ private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[loc] currentDefs, set[Define] newDefs) { // Is newName already resolvable from a scope where is currently declared? - rel[loc old, loc new] doubleDeclarations = { | loc cD <- currentDefs - , Define nD <- newDefs + rel[loc old, loc new] doubleDeclarations = { | <- (currentDefs * newDefs) , isContainedIn(cD, nD.scope) , !rascalMayOverload({cD, nD.defined}, ws.definitions) }; @@ -260,7 +259,7 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { } }; - if (size(locsContainingCursor) == 0) { + if (locsContainingCursor == {}) { throw unsupportedRename("Cannot find type information in TPL for "); } @@ -292,8 +291,6 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { // The cursor is at a collection field cur = cursor(collectionField(), c, cursorName); } - } else { - fail; } } case {k}: { @@ -323,7 +320,8 @@ private bool containsName(loc l, str name) { return false; } -list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { +list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) + = job("renaming to ", list[DocumentEdit](void(str, int) step) { loc cursorLoc = cursorT.src; str cursorName = ""; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 7ee6e6803..46d4cc443 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -37,8 +37,16 @@ import util::Reflective; import lang::rascal::\syntax::Rascal; -Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) = - (nothing() | (it == nothing() || (just(itt) := it && w < itt)) && isContainedIn(l, w) ? just(w) : it | w <- wrappers); +Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) { + Maybe[loc] result = nothing(); + for (w <- wrappers, isContainedIn(l, w)) { + switch (result) { + case just(loc current): if (w < current) result = just(w); + case nothing(): result = just(w); + } + } + return result; +} loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { assert l.begin.line == l.end.line : diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 3cdf10de5..4a2445920 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -50,14 +50,17 @@ import Map; import Set; import String; -data CursorKind = use() - | def() - | typeParam() - | collectionField() - | moduleName() - ; - -data Cursor = cursor(CursorKind kind, loc l, str name); +data CursorKind + = use() + | def() + | typeParam() + | collectionField() + | moduleName() + ; + +data Cursor + = cursor(CursorKind kind, loc l, str name) + ; alias MayOverloadFun = bool(set[loc] defs, map[loc, Define] defines); alias FileRenamesF = rel[loc old, loc new](str newName); From 995a651988ef039d2d3fddf36c55ffd37e28798e Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 1 Aug 2024 15:09:51 +0200 Subject: [PATCH 058/105] Document WorkspaceInfo. --- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 4a2445920..981b955a7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -67,6 +67,10 @@ alias FileRenamesF = rel[loc old, loc new](str newName); alias DefsUsesRenames = tuple[set[loc] defs, set[loc] uses, FileRenamesF renames]; alias ProjectFiles = rel[loc projectFolder, loc file]; +/** + * This is a subset of the fields from analysis::typepal::TModel, specifically tailored to refactorings. + * WorkspaceInfo comes with a set of functions that allow (incrementally) loading information from multiple TModels, and doing (cached) queries on this data. + */ data WorkspaceInfo ( // Instance fields // Read-only @@ -86,6 +90,8 @@ WorkspaceInfo loadLocs(WorkspaceInfo ws, ProjectFiles projectFiles) { for (tm <- ws.tmodelsForLocs(projectFiles)) { ws = loadTModel(ws, tm); } + + // In addition to data from the TModel, we keep track of which projects/modules we loaded. ws.modules += projectFiles.file; ws.projects += projectFiles.projectFolder; From d30a717084016051261a54b22662026729a06527 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 5 Aug 2024 15:14:20 +0200 Subject: [PATCH 059/105] Fixed comments from review by @rodinaarssen. --- .../src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc | 4 ++-- .../src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc | 2 -- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 5 +++-- .../src/main/rascal/lang/rascal/tests/rename/Functions.rsc | 1 - .../src/main/rascal/lang/rascal/tests/rename/Modules.rsc | 1 - .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 7 ++----- 6 files changed, 7 insertions(+), 13 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc index a4ab161f2..64e334800 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc @@ -36,11 +36,11 @@ import util::Benchmark; import util::FileSystem; import util::Reflective; -void main() { +void benchmarkDeepTreeMatch(loc projectPath) { name = "val"; println("Collecting files..."); - fs = find(|home:///swat/projects/Rascal/rascal/src|, "rsc"); + fs = find(projectPath, "rsc"); println("Parsing modules..."); trees = {parseModuleWithSpaces(f) | f <- fs}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc index b578b117d..5121a1990 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Exception.rsc @@ -31,8 +31,6 @@ import analysis::typepal::TModel; import Message; import Set; -data Cursor; - alias Capture = tuple[loc def, loc use]; data IllegalRenameReason diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 168da147a..97485df68 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -234,6 +234,7 @@ private bool isFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)) = private bool isFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; private bool isFunctionLocal(WorkspaceInfo _, cursor(collectionField(), _, _)) = false; private bool isFunctionLocal(WorkspaceInfo _, cursor(moduleName(), _, _)) = false; +private default bool isFunctionLocal(_, _) = false; tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; @@ -308,8 +309,8 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { private bool containsName(loc l, str name) { // If we do not find any occurrences of the name under the cursor in a module, // we are not interested in it at all, and will skip loading its TPL. - cursorAsName = [Name] name; - escapedCursorAsName = startsWith(name, "\\") ? name : [Name] "\\"; + Name cursorAsName = [Name] name; + Name escapedCursorAsName = startsWith(name, "\\") ? name : [Name] "\\"; m = parseModuleWithSpacesCached(l); if (/cursorAsName := m) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index b59fbb757..14ea96000 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -27,7 +27,6 @@ POSSIBILITY OF SUCH DAMAGE. module lang::rascal::tests::rename::Functions import lang::rascal::tests::rename::TestUtils; -// import lang::rascal::lsp::refactor::Exception; test bool nestedFunctionParameter() = {0, 1} == testRenameOccurrences(" 'int f(int foo, int baz) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc index 565442a37..17630c566 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc @@ -27,7 +27,6 @@ POSSIBILITY OF SUCH DAMAGE. module lang::rascal::tests::rename::Modules import lang::rascal::tests::rename::TestUtils; -// import lang::rascal::lsp::refactor::Exception; test bool singleModuleFromHeader() = testRenameOccurrences({ byText("Foo", " diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 590e70c34..5152c5885 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -138,7 +138,7 @@ set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, // Test renames that are expected to throw an exception bool testRename(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { - = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); + edits = getEdits(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); print("UNEXPECTED EDITS: "); iprintln(edits); @@ -172,7 +172,7 @@ PathConfig getRascalCorePathConfig(loc rascalCoreProject, loc typepalProject) { PathConfig resolveLocations(PathConfig pcfg) { visit(pcfg) { - case loc l: resolveLocation(l); + case loc l => resolveLocation(l) } return pcfg; @@ -283,6 +283,3 @@ private loc storeTestModule(loc dir, str name, str body) { private set[Tree] occsToTrees(start[Module] m, str name, set[int] occs) = {n | i <- occs, n := [n | /Name n := m.top, "" == name][i]}; private set[loc] occsToLocs(start[Module] m, str name, set[int] occs) = {t.src | t <- occsToTrees(m, name, occs)}; private set[int] locsToOccs(start[Module] m, str name, set[loc] occs) = {indexOf(names, occ) | names := [n.src | /Name n := m.top, "" == name], occ <- occs}; - -// Workaround to be able to pattern match on the emulated `src` field -data Tree (loc src = |unknown:///|(0,0,<0,0>,<0,0>)); From 051f4c7cfc2b592a568247a12d2b3e05f99c9a05 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 5 Aug 2024 15:23:04 +0200 Subject: [PATCH 060/105] Fix type error. --- rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 97485df68..c22c9630b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -310,7 +310,7 @@ private bool containsName(loc l, str name) { // If we do not find any occurrences of the name under the cursor in a module, // we are not interested in it at all, and will skip loading its TPL. Name cursorAsName = [Name] name; - Name escapedCursorAsName = startsWith(name, "\\") ? name : [Name] "\\"; + Name escapedCursorAsName = startsWith(name, "\\") ? cursorAsName : [Name] "\\"; m = parseModuleWithSpacesCached(l); if (/cursorAsName := m) { From cfccd8e53b0f94c9715a16aa35fd2329813ec80d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 5 Aug 2024 15:23:57 +0200 Subject: [PATCH 061/105] Fix `resolveLocations` --- .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 5152c5885..bf53942d4 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -171,11 +171,9 @@ PathConfig getRascalCorePathConfig(loc rascalCoreProject, loc typepalProject) { } PathConfig resolveLocations(PathConfig pcfg) { - visit(pcfg) { + return visit(pcfg) { case loc l => resolveLocation(l) - } - - return pcfg; + }; } PathConfig getPathConfig(loc project) { From bb69e9301862f7c53bea17b8ee3b91ddfe656628 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 5 Aug 2024 16:37:19 +0200 Subject: [PATCH 062/105] Implement type param double declaration detection. --- .../lang/rascal/lsp/refactor/Rename.rsc | 20 ++++++++++++---- .../lang/rascal/tests/rename/Functions.rsc | 23 ++++++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index c22c9630b..dd6621db7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -88,7 +88,7 @@ set[IllegalRenameReason] checkLegalName(str name) { private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = { definitionsOutsideWorkspace(d) | set[loc] d <- groupRangeByDomain({ | loc d <- defs, f := d.top, f notin ws.modules}) }; -private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[loc] currentDefs, set[Define] newDefs) { +private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[loc] currentDefs, set[Define] newDefs, str newName) { // Is newName already resolvable from a scope where is currently declared? rel[loc old, loc new] doubleDeclarations = { | <- (currentDefs * newDefs) , isContainedIn(cD, nD.scope) @@ -106,7 +106,19 @@ private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, , isStrictlyContainedIn(nD, fL) }; - return {doubleDeclaration(old, doubleDeclarations[old]) | old <- (doubleDeclarations + doubleFieldDeclarations).old}; + rel[loc old, loc new] doubleTypeParamDeclarations = { + | loc cD <- currentDefs + , ws.facts[cD]? + , cT: aparameter(_, _) := ws.facts[cD] + , Define fD: <_, _, _, _, _, defType(afunc(_, funcParams:/cT, _))> <- ws.defines + , isContainedIn(cD, fD.defined) + , loc nD <- ws.facts + , isContainedIn(nD, fD.defined) + , nT: aparameter(newName, _) := ws.facts[nD] + , /nT := funcParams + }; + + return {doubleDeclaration(old, doubleDeclarations[old]) | old <- (doubleDeclarations + doubleFieldDeclarations + doubleTypeParamDeclarations).old}; } private set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { @@ -158,7 +170,7 @@ private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[M return checkLegalName(newName) + checkDefinitionsOutsideWorkspace(ws, currentDefs) - + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs) + + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs, newName) + checkCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs) ; } @@ -206,7 +218,7 @@ private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTex } private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[loc] defs, set[loc] uses, str name) = - computeTextEdits(ws, parseModuleWithSpaces(moduleLoc), defs, uses, name); + computeTextEdits(ws, parseModuleWithSpacesCached(moduleLoc), defs, uses, name); private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { set[str] names = {definitions[l].id | l <- defs, definitions[l]?}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index 14ea96000..60bbde973 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -193,10 +193,31 @@ test bool typeParamsFromFormal() = {0, 1, 2} == testRenameOccurrences(" '} ", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); -test bool typeParamsClash() = {0, 1} == testRenameOccurrences(" +@expected{illegalRename} +test bool typeParamsClash() = testRename(" '&T foo(&T l, &U m) = l; ", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); +test bool nestedTypeParams() = {0, 1, 2, 3} == testRenameOccurrences(" + '&T f(&T t) { + ' &T g(&T t) = t; + ' return g(t); + '} +", oldName = "T", newName = "U"); + +@expected{illegalRename} +test bool nestedTypeParamClash() = testRename(" + 'void f(&S s, &T t) { + ' &S g(&S s) = s; + ' &T g(&T t) = t; + } +", oldName = "S", newName = "T", cursorAtOldNameOccurrence = 1); + +test bool adjacentTypeParams() = {0, 1} == testRenameOccurrences(" + '&S f(&S s) = s; + '&T f(&T t) = t; +", oldName = "S", newName = "T"); + test bool typeParamsListReturn() = {0, 1, 2} == testRenameOccurrences(" 'list[&T] foo(&T l) { ' list[&T] m = [l, l]; From 3b04ea415838e88791cba0c06024aef09b6fced8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 6 Aug 2024 15:04:49 +0200 Subject: [PATCH 063/105] Support renaming of keyword parameters. --- .../lang/rascal/lsp/refactor/Rename.rsc | 1 + .../rascal/lsp/refactor/WorkspaceInfo.rsc | 25 ++++++++++++++++--- .../rascal/tests/rename/FormalParameters.rsc | 14 +++++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index dd6621db7..629ce6a92 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -199,6 +199,7 @@ Maybe[loc] locationOfName(Name n) = just(n.src); Maybe[loc] locationOfName(QualifiedName qn) = just((qn.names[-1]).src); Maybe[loc] locationOfName(FunctionDeclaration f) = just(f.signature.name.src); Maybe[loc] locationOfName(Variable v) = just(v.name.src); +Maybe[loc] locationOfName(KeywordFormal kw) = just(kw.name.src); Maybe[loc] locationOfName(Declaration d) = just(d.name.src) when d is annotation || d is \tag; Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is \alias diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 981b955a7..7ca50ab8b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -156,15 +156,34 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv private rel[loc, loc] NO_RENAMES(str _) = {}; private int qualSepSize = size("::"); +set[loc] getKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { + set[loc] uses = {}; + + ifacts = invert(ws.facts); + for (d <- defs + , f <- ws.facts + , isStrictlyContainedIn(d, f) + , afunc(retType, _, [*_, kwField(kwAType, cursorName, _), *_]) := ws.facts[f] + , funcName <- getUses(ws, f) + ) { + loc funcCall = min({l | l <- ifacts[retType], l.offset == funcName.offset}); + uses += {name.src | /Name name := parseModuleWithSpacesCached(funcCall.top) + , isStrictlyContainedIn(name.src, funcCall) + , "" == kwAType.alabel}; + } + + return uses; +} + DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { defs = getOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); - uses = getUses(ws, defs); + uses = getUses(ws, defs) + getKeywordFormalUses(ws, defs, cursorName); return ; } -DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(def(), l, _), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { defs = getOverloadedDefs(ws, {l}, mayOverloadF); - uses = getUses(ws, defs); + uses = getUses(ws, defs) + getKeywordFormalUses(ws, defs, cursorName); return ; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc index 478037d5f..2cb8f4854 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc @@ -65,17 +65,21 @@ test bool privateFunctionParameter() = {0, 1} == testRenameOccurrences("", decls '} "); -@expected{unsupportedRename} test bool nestedKeywordParameter() = {0, 1, 2} == testRenameOccurrences(" 'int f(int foo = 8) = foo; 'int x = f(foo = 10); "); -@expected{unsupportedRename} -test bool keywordParameter() = testRename( +test bool keywordParameterFromDef() = ASSERT_EQ({0, 1, 2}, testRenameOccurrences( "int x = f(foo = 10);" - decls="int f(int foo = 8) = foo;" -); + , decls="int f(int foo = 8) = foo;" +)); + +test bool keywordParameterFromUse() = ASSERT_EQ({0, 1, 2}, testRenameOccurrences( + "int x = f(foo = 10);" + , decls="int f(int foo = 8) = foo;" + , cursorAtOldNameOccurrence = 1 +)); @expected{illegalRename} test bool doubleParameterDeclaration1() = testRename("int f(int foo, int bar) = 1;"); @expected{illegalRename} test bool doubleParameterDeclaration2() = testRename("int f(int bar, int foo) = 1;"); From d8ecd2b48dacab0c8d3618ac0998f392f970d5e3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 7 Aug 2024 09:40:25 +0200 Subject: [PATCH 064/105] Remove unused imports. --- .../src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 3 --- 1 file changed, 3 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 7ca50ab8b..f0abde640 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -35,15 +35,12 @@ import lang::rascalcore::check::Checker; import lang::rascal::\syntax::Rascal; -import util::FileSystem; import util::Maybe; -import util::Monitor; import util::Reflective; import lang::rascal::lsp::refactor::Exception; import lang::rascal::lsp::refactor::Util; -import IO; import List; import Location; import Map; From 65d91e7fbff301d5af8ca38c5199dcd74421c275 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 2 Aug 2024 17:25:39 +0200 Subject: [PATCH 065/105] Improve finding names in definitions. --- .../rascal/lang/rascal/lsp/refactor/Rename.rsc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 629ce6a92..069e1de4d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -39,6 +39,7 @@ import Exception; import IO; import List; import Location; +import Map; import ParseTree; import Relation; import Set; @@ -179,20 +180,22 @@ private str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\< // Find the smallest trees of defined non-terminal type with a source location in `useDefs` private set[loc] findNames(start[Module] m, set[loc] useDefs) { - set[loc] names = {}; + map[loc, loc] useDefNameAt = (); + useDefsToDo = useDefs; visit(m.top) { case t: appl(prod(_, _, _), _): { - if (t.src in useDefs && just(nameLoc) := locationOfName(t)) { - names += nameLoc; + if (t.src in useDefsToDo && just(nameLoc) := locationOfName(t)) { + useDefNameAt[t.src] = nameLoc; + useDefsToDo -= t.src; } } } - if (size(names) != size(useDefs)) { - throw unsupportedRename("Rename unsupported", issues={."> | l <- useDefs - names}); + if (useDefsToDo != {}) { + throw unsupportedRename("Rename unsupported", issues={."> | l <- useDefsToDo}); } - return names; + return range(useDefNameAt); } Maybe[loc] locationOfName(Name n) = just(n.src); From a87f100707573d92260ba40e4fe87636c0198b2e Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 7 Aug 2024 10:29:06 +0200 Subject: [PATCH 066/105] Improve user-facing errors. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 6 ++---- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 069e1de4d..e244ac49f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -277,7 +277,7 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { }; if (locsContainingCursor == {}) { - throw unsupportedRename("Cannot find type information in TPL for "); + throw unsupportedRename("Renaming \'\' at is not supported."); } loc c = min(locsContainingCursor.l); @@ -313,11 +313,9 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { case {k}: { cur = cursor(k, c, cursorName); } - default: - throw unsupportedRename("Unsupported cursor type "); } - if (cur.l.scheme == "unknown") throw unexpectedFailure("Could not find cursor location."); + if (cur.l.scheme == "unknown") throw unsupportedRename("Could not retrieve information for \'\' at ."); return ; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index f0abde640..1918bcc98 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -280,10 +280,6 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLo } } - if (defs == {}) { - throw unsupportedRename("Cannot rename field that is not declared inside this workspace."); - } - return ; } From e6849ae146d8367df8ed207e38c286a0abd3895a Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 7 Aug 2024 17:11:08 +0200 Subject: [PATCH 067/105] Separate Rasal-specific from generic by name. --- .../lsp/rascal/RascalLanguageServices.java | 2 +- .../lang/rascal/lsp/refactor/Rename.rsc | 110 +++++++++--------- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 6 +- .../lang/rascal/tests/rename/TestUtils.rsc | 4 +- 4 files changed, 62 insertions(+), 60 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java index 504f631e8..4dd0e378b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java @@ -199,7 +199,7 @@ public InterruptibleFuture getRename(ITree module, Position cursor, Set { try { IFunction rascalGetPathConfig = eval.getFunctionValueFactory().function(getPathConfigType, (t, u) -> addResources(getPathConfig.apply((ISourceLocation) t[0]))); - return (IList) eval.call("renameRascalSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), VF.string(newName), rascalGetPathConfig); + return (IList) eval.call("rascalRenameSymbol", cursorTree, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), VF.string(newName), rascalGetPathConfig); } catch (Throw e) { if (e.getException() instanceof IConstructor) { var exception = (IConstructor)e.getException(); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index e244ac49f..878ea1aa7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -67,29 +67,31 @@ import util::Maybe; import util::Monitor; import util::Reflective; +// Rascal compiler-specific extension void throwAnyErrors(list[ModuleMessages] mmsgs) { for (mmsg <- mmsgs) { throwAnyErrors(mmsg); } } +// Rascal compiler-specific extension void throwAnyErrors(program(_, msgs)) { throwAnyErrors(msgs); } -set[IllegalRenameReason] checkLegalName(str name) { +set[IllegalRenameReason] rascalCheckLegalName(str name) { try { - parse(#Name, escapeName(name)); + parse(#Name, rascalEscapeName(name)); return {}; } catch ParseError(_): { return {invalidName(name)}; } } -private set[IllegalRenameReason] checkDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = - { definitionsOutsideWorkspace(d) | set[loc] d <- groupRangeByDomain({ | loc d <- defs, f := d.top, f notin ws.modules}) }; +private set[IllegalRenameReason] rascalCheckDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) = + { definitionsOutsideWorkspace(d) | set[loc] d <- groupRangeByDomain({ | loc d <- defs, f := d.top, f notin ws.sourceFiles}) }; -private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, set[loc] currentDefs, set[Define] newDefs, str newName) { +private set[IllegalRenameReason] rascalCheckCausesDoubleDeclarations(WorkspaceInfo ws, set[loc] currentDefs, set[Define] newDefs, str newName) { // Is newName already resolvable from a scope where is currently declared? rel[loc old, loc new] doubleDeclarations = { | <- (currentDefs * newDefs) , isContainedIn(cD, nD.scope) @@ -122,14 +124,14 @@ private set[IllegalRenameReason] checkCausesDoubleDeclarations(WorkspaceInfo ws, return {doubleDeclaration(old, doubleDeclarations[old]) | old <- (doubleDeclarations + doubleFieldDeclarations + doubleTypeParamDeclarations).old}; } -private set[Define] findImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { - set[loc] maybeImplicitDefs = {l | /QualifiedName n := m, just(l) := locationOfName(n)}; - return {def | Define def <- newDefs, (def.idRole is variableId && def.defined in ws.useDef<0>) - || (def.idRole is patternVariableId && def.defined in maybeImplicitDefs)}; -} +private set[IllegalRenameReason] rascalCheckCausesCaptures(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, set[Define] newDefs) { + set[Define] rascalFindImplicitDefinitions(WorkspaceInfo ws, start[Module] m, set[Define] newDefs) { + set[loc] maybeImplicitDefs = {l | /QualifiedName n := m, just(l) := rascalLocationOfName(n)}; + return {def | Define def <- newDefs, (def.idRole is variableId && def.defined in ws.useDef<0>) + || (def.idRole is patternVariableId && def.defined in maybeImplicitDefs)}; + } -private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, set[Define] newDefs) { - set[Define] newNameImplicitDefs = findImplicitDefinitions(ws, m, newDefs); + set[Define] newNameImplicitDefs = rascalFindImplicitDefinitions(ws, m, newDefs); // Will this rename turn an implicit declaration of `newName` into a use of a current declaration? set[Capture] implicitDeclBecomesUseOfCurrentDecl = @@ -165,26 +167,26 @@ private set[IllegalRenameReason] checkCausesCaptures(WorkspaceInfo ws, start[Mod return allCaptures == {} ? {} : {captureChange(allCaptures)}; } -private set[IllegalRenameReason] collectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, str newName) { +private set[IllegalRenameReason] rascalCollectIllegalRenames(WorkspaceInfo ws, start[Module] m, set[loc] currentDefs, set[loc] currentUses, str newName) { set[Define] newNameDefs = {def | Define def:<_, newName, _, _, _, _> <- ws.defines}; return - checkLegalName(newName) - + checkDefinitionsOutsideWorkspace(ws, currentDefs) - + checkCausesDoubleDeclarations(ws, currentDefs, newNameDefs, newName) - + checkCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs) + rascalCheckLegalName(newName) + + rascalCheckDefinitionsOutsideWorkspace(ws, currentDefs) + + rascalCheckCausesDoubleDeclarations(ws, currentDefs, newNameDefs, newName) + + rascalCheckCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs) ; } -private str escapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; +private str rascalEscapeName(str name) = name in getRascalReservedIdentifiers() ? "\\" : name; // Find the smallest trees of defined non-terminal type with a source location in `useDefs` -private set[loc] findNames(start[Module] m, set[loc] useDefs) { +private set[loc] rascalFindNamesInUseDefs(start[Module] m, set[loc] useDefs) { map[loc, loc] useDefNameAt = (); useDefsToDo = useDefs; visit(m.top) { case t: appl(prod(_, _, _), _): { - if (t.src in useDefsToDo && just(nameLoc) := locationOfName(t)) { + if (t.src in useDefsToDo && just(nameLoc) := rascalLocationOfName(t)) { useDefNameAt[t.src] = nameLoc; useDefsToDo -= t.src; } @@ -198,27 +200,27 @@ private set[loc] findNames(start[Module] m, set[loc] useDefs) { return range(useDefNameAt); } -Maybe[loc] locationOfName(Name n) = just(n.src); -Maybe[loc] locationOfName(QualifiedName qn) = just((qn.names[-1]).src); -Maybe[loc] locationOfName(FunctionDeclaration f) = just(f.signature.name.src); -Maybe[loc] locationOfName(Variable v) = just(v.name.src); -Maybe[loc] locationOfName(KeywordFormal kw) = just(kw.name.src); -Maybe[loc] locationOfName(Declaration d) = just(d.name.src) when d is annotation +Maybe[loc] rascalLocationOfName(Name n) = just(n.src); +Maybe[loc] rascalLocationOfName(QualifiedName qn) = just((qn.names[-1]).src); +Maybe[loc] rascalLocationOfName(FunctionDeclaration f) = just(f.signature.name.src); +Maybe[loc] rascalLocationOfName(Variable v) = just(v.name.src); +Maybe[loc] rascalLocationOfName(KeywordFormal kw) = just(kw.name.src); +Maybe[loc] rascalLocationOfName(Declaration d) = just(d.name.src) when d is annotation || d is \tag; -Maybe[loc] locationOfName(Declaration d) = locationOfName(d.user.name) when d is \alias +Maybe[loc] rascalLocationOfName(Declaration d) = rascalLocationOfName(d.user.name) when d is \alias || d is dataAbstract || d is \data; -Maybe[loc] locationOfName(TypeVar tv) = just(tv.name.src); -Maybe[loc] locationOfName(Header h) = locationOfName(h.name); -default Maybe[loc] locationOfName(Tree t) = nothing(); +Maybe[loc] rascalLocationOfName(TypeVar tv) = just(tv.name.src); +Maybe[loc] rascalLocationOfName(Header h) = rascalLocationOfName(h.name); +default Maybe[loc] rascalLocationOfName(Tree t) = nothing(); private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[loc] defs, set[loc] uses, str name) { - if (reasons := collectIllegalRenames(ws, m, defs, uses, name), reasons != {}) { + if (reasons := rascalCollectIllegalRenames(ws, m, defs, uses, name), reasons != {}) { return ; } - replaceName = escapeName(name); - return <{}, [replace(l, replaceName) | l <- findNames(m, defs + uses)]>; + replaceName = rascalEscapeName(name); + return <{}, [replace(l, replaceName) | l <- rascalFindNamesInUseDefs(m, defs + uses)]>; } private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[loc] defs, set[loc] uses, str name) = @@ -232,7 +234,7 @@ private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitio return rascalMayOverload(defs, potentialOverloadDefinitions); } -private bool isFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { +private bool rascalIsFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { for (d <- defs) { if (Define _: <_, _, _, _, funDef, defType(afunc(_, _, _))> <- ws.defines , isContainedIn(ws.definitions[d].scope, funDef)) { @@ -243,16 +245,16 @@ private bool isFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { return true; } -private bool isFunctionLocal(WorkspaceInfo ws, cursor(def(), cursorLoc, _)) = - isFunctionLocalDefs(ws, getOverloadedDefs(ws, {cursorLoc}, rascalMayOverloadSameName)); -private bool isFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)) = - isFunctionLocalDefs(ws, getOverloadedDefs(ws, getDefs(ws, cursorLoc), rascalMayOverloadSameName)); -private bool isFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; -private bool isFunctionLocal(WorkspaceInfo _, cursor(collectionField(), _, _)) = false; -private bool isFunctionLocal(WorkspaceInfo _, cursor(moduleName(), _, _)) = false; -private default bool isFunctionLocal(_, _) = false; +private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(def(), cursorLoc, _)) = + rascalIsFunctionLocalDefs(ws, getOverloadedDefs(ws, {cursorLoc}, rascalMayOverloadSameName)); +private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)) = + rascalIsFunctionLocalDefs(ws, getOverloadedDefs(ws, getDefs(ws, cursorLoc), rascalMayOverloadSameName)); +private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; +private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(collectionField(), _, _)) = false; +private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(moduleName(), _, _)) = false; +private default bool rascalIsFunctionLocal(_, _) = false; -tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { +tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; str cursorName = ""; @@ -272,7 +274,7 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { // Collection field uses; any location which is of set or list type, where the label of the collection element equals the name under the cursor , // Module name declaration, where the cursor location is in the module header - , + , } }; @@ -320,22 +322,22 @@ tuple[Cursor, WorkspaceInfo] getCursor(WorkspaceInfo ws, Tree cursorT) { return ; } +private set[Name] rascalNameToEquivalentNames(str name) = { + [Name] name, + startsWith(name, "\\") ? [Name] name : [Name] "\\" +}; + private bool containsName(loc l, str name) { // If we do not find any occurrences of the name under the cursor in a module, // we are not interested in it at all, and will skip loading its TPL. - Name cursorAsName = [Name] name; - Name escapedCursorAsName = startsWith(name, "\\") ? cursorAsName : [Name] "\\"; - m = parseModuleWithSpacesCached(l); - if (/cursorAsName := m) { - return true; - } else if (escapedCursorAsName != cursorAsName, /escapedCursorAsName := m) { - return true; + for (n <- rascalNameToEquivalentNames(name)) { + if (/n := m) return true; } return false; } -list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) +list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { loc cursorLoc = cursorT.src; str cursorName = ""; @@ -379,10 +381,10 @@ list[DocumentEdit] renameRascalSymbol(Tree cursorT, set[loc] workspaceFolders, s ); step("analyzing name at cursor", 1); - = getCursor(ws, cursorT); + = rascalGetCursor(ws, cursorT); step("loading required type information", 1); - if (!isFunctionLocal(ws, cur)) { + if (!rascalIsFunctionLocal(ws, cur)) { ws = loadWorkspace(ws); } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 1918bcc98..468e10ce9 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -73,7 +73,7 @@ data WorkspaceInfo ( // Read-only rel[loc use, loc def] useDef = {}, set[Define] defines = {}, - set[loc] modules = {}, + set[loc] sourceFiles = {}, map[loc, Define] definitions = (), map[loc, AType] facts = (), set[loc] projects = {} @@ -89,7 +89,7 @@ WorkspaceInfo loadLocs(WorkspaceInfo ws, ProjectFiles projectFiles) { } // In addition to data from the TModel, we keep track of which projects/modules we loaded. - ws.modules += projectFiles.file; + ws.sourceFiles += projectFiles.file; ws.projects += projectFiles.projectFolder; return ws; @@ -107,7 +107,7 @@ WorkspaceInfo loadTModel(WorkspaceInfo ws, TModel tm) { try { throwAnyErrors(tm); } catch set[Message] errors: { - throw unsupportedRename("Cannot rename: some modules in workspace have errors.\n", issues={<(error.at ? |unknown:///|), error.msg> | error <- errors}); + throw unsupportedRename("Cannot rename: some files in workspace have errors.\n", issues={<(error.at ? |unknown:///|), error.msg> | error <- errors}); } ws.useDef += tm.useDef; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index bf53942d4..d7d26ee2e 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -96,7 +96,7 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.expectedRenameOccs, newName = m.newName))}; cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); - edits = renameRascalSymbol(cursorT, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); + edits = rascalRenameSymbol(cursorT, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); renamesPerModule = ( beforeRename: afterRename @@ -204,7 +204,7 @@ list[DocumentEdit] getEdits(loc singleModule, set[loc] projectDirs, int cursorAt m = parseModuleWithSpaces(f); Tree cursor = [n | /Name n := m.top, "" == oldName][cursorAtOldNameOccurrence]; - return renameRascalSymbol(cursor, projectDirs, newName, getPathConfig); + return rascalRenameSymbol(cursor, projectDirs, newName, getPathConfig); } tuple[list[DocumentEdit], set[int]] getEditsAndOccurrences(loc singleModule, loc projectDir, int cursorAtOldNameOccurrence, str oldName, str newName, PathConfig pcfg = getTestPathConfig(projectDir)) { From f9ec5e662a711d6cb2d1de58edebebcf21f2c295 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 8 Aug 2024 14:14:04 +0200 Subject: [PATCH 068/105] Cache inverted WorkspaceInfo fields. --- .../rascal/lang/rascal/lsp/refactor/Rename.rsc | 3 +-- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 15 ++++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 878ea1aa7..5b4d8d001 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -149,13 +149,12 @@ private set[IllegalRenameReason] rascalCheckCausesCaptures(WorkspaceInfo ws, sta }; // Will this rename hide a used definition of `newName` behind a definition of `oldName` (shadowing)? - iUseDef = invert(ws.useDef); set[Capture] newUseShadowedByRename = { | Define nD <- newDefs , loc cD <- currentDefs , loc cS := ws.definitions[cD].scope , isContainedIn(cS, nD.scope) - , nU <- iUseDef[newDefs.defined] + , nU <- defUse(ws)[newDefs.defined] , isContainedIn(nU, cS) }; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 468e10ce9..03b7ee0e2 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -126,9 +126,15 @@ loc getProjectFolder(WorkspaceInfo ws, loc l) { throw "Could not find project containing "; } -set[loc] getUses(WorkspaceInfo ws, loc def) = invert(ws.useDef)[def]; +@memo{maximumSize=1, minutes=5} +rel[loc, loc] defUse(WorkspaceInfo ws) = invert(ws.useDef); -set[loc] getUses(WorkspaceInfo ws, set[loc] defs) = invert(ws.useDef)[defs]; +@memo{maximumSize=1, minutes=5} +map[AType, set[loc]] factsInvert(WorkspaceInfo ws) = invert(ws.facts); + +set[loc] getUses(WorkspaceInfo ws, loc def) = defUse(ws)[def]; + +set[loc] getUses(WorkspaceInfo ws, set[loc] defs) = defUse(ws)[defs]; set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; @@ -156,14 +162,13 @@ private int qualSepSize = size("::"); set[loc] getKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = {}; - ifacts = invert(ws.facts); for (d <- defs , f <- ws.facts , isStrictlyContainedIn(d, f) , afunc(retType, _, [*_, kwField(kwAType, cursorName, _), *_]) := ws.facts[f] , funcName <- getUses(ws, f) ) { - loc funcCall = min({l | l <- ifacts[retType], l.offset == funcName.offset}); + loc funcCall = min({l | l <- factsInvert(ws)[retType], l.offset == funcName.offset}); uses += {name.src | /Name name := parseModuleWithSpacesCached(funcCall.top) , isStrictlyContainedIn(name.src, funcCall) , "" == kwAType.alabel}; @@ -242,7 +247,7 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLo } } - set[loc] collectionFacts = invert(ws.facts)[collectionType]; + set[loc] collectionFacts = factsInvert(ws)[collectionType]; rel[loc file, loc u] factsByModule = groupBy(collectionFacts, loc(loc l) { return l.top; }); set[loc] defs = {}; From 864e98f207db35a144c086aa9d980f1573e1ee84 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 13 Aug 2024 17:35:43 +0200 Subject: [PATCH 069/105] Resolve definitions properly. --- .../lang/rascal/lsp/refactor/Rename.rsc | 6 +- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 53 +++++++-- .../lang/rascal/tests/rename/Functions.rsc | 55 ++++++++++ .../lang/rascal/tests/rename/TestUtils.rsc | 4 +- .../rascal/lang/rascal/tests/rename/Types.rsc | 102 ++++++++++++++++++ 5 files changed, 209 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 5b4d8d001..2578a5b63 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -372,8 +372,10 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true] [verbose = false] [logPathConfig = false]; - ms = rascalTModelForLocs(toList(\files), ccfg, dummy_compile1); - tmodels += {convertTModel2PhysicalLocs(tm) | m <- ms.tmodels, tm := ms.tmodels[m]}; + for (file <- \files) { + ms = rascalTModelForLocs([file], ccfg, dummy_compile1); + tmodels += {convertTModel2PhysicalLocs(tm) | m <- ms.tmodels, tm := ms.tmodels[m]}; + } } return tmodels; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 03b7ee0e2..cc5b17133 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -76,6 +76,8 @@ data WorkspaceInfo ( set[loc] sourceFiles = {}, map[loc, Define] definitions = (), map[loc, AType] facts = (), + Scopes scopes = (), + Paths paths = {}, set[loc] projects = {} ) = workspaceInfo( ProjectFiles() preloadFiles, @@ -114,6 +116,8 @@ WorkspaceInfo loadTModel(WorkspaceInfo ws, TModel tm) { ws.defines += tm.defines; ws.definitions += tm.definitions; ws.facts += tm.facts; + ws.scopes += tm.scopes; + ws.paths += tm.paths; return ws; } @@ -140,20 +144,50 @@ set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; Maybe[AType] getFact(WorkspaceInfo ws, loc l) = l in ws.facts ? just(ws.facts[l]) : nothing(); +@memo{maximumSize=1, minutes=5} +set[loc] getModuleScopes(WorkspaceInfo ws) = invert(ws.scopes)[|global-scope:///|]; + +@memo{maximumSize=1, minutes=5} +map[loc, loc] getModuleScopePerFile(WorkspaceInfo ws) = (scope.top: scope | loc scope <- getModuleScopes(ws)); + +@memo{maximumSize=1, minutes=5} +rel[loc from, loc to] getTransitiveReflexiveModulePaths(WorkspaceInfo ws) { + rel[loc from, loc to] moduleI = ident(getModuleScopes(ws)); + rel[loc from, loc to] imports = (ws.paths)[importPath()]; + rel[loc from, loc to] extends = (ws.paths)[extendPath()]; + + return (moduleI + imports) // o or 1 imports + o (moduleI + extends+) // 0 or more extends + ; +} + set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { - set[loc] overloadedLocs = defs; + set[loc] overloadedDefs = defs; // Pre-condition - assert mayOverloadF(overloadedLocs, ws.definitions): + assert mayOverloadF(overloadedDefs, ws.definitions): "Initial defs are invalid overloads!"; - for (loc d <- ws.definitions) { - if (mayOverloadF(defs + d, ws.definitions)) { - overloadedLocs += d; - } + map[loc file, loc scope] moduleScopePerFile = getModuleScopePerFile(ws); + rel[loc d, loc scope] scopesOfDefUses = { | <- ws.useDef}; + rel[loc def, loc scope] defAndTheirUseScopes = ws.defines + scopesOfDefUses; + rel[loc from, loc to] modulePaths = getTransitiveReflexiveModulePaths(ws); + + solve(overloadedDefs) { + rel[loc from, loc to] reachableDefs = + ident(overloadedDefs) // Start from all current defs + o defAndTheirUseScopes // - Look up their scope and scopes of their uses + o modulePaths // - Follow import/extend relations to reachable scopes + o ws.defines // - Find definitions in the reached scope + ; + + overloadedDefs += {d + | loc d <- reachableDefs<1> + , mayOverloadF(overloadedDefs + d, ws.definitions) + }; } - return overloadedLocs; + return overloadedDefs; } private rel[loc, loc] NO_RENAMES(str _) = {}; @@ -184,8 +218,11 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayO } DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { - defs = getOverloadedDefs(ws, {l}, mayOverloadF); + set[loc] initialUses = getUses(ws, l); + set[loc] initialDefs = {l} + {*ds | u <- initialUses, ds := getDefs(ws, u)}; + defs = getOverloadedDefs(ws, initialDefs, mayOverloadF); uses = getUses(ws, defs) + getKeywordFormalUses(ws, defs, cursorName); + return ; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index 60bbde973..ffe3266d7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -231,3 +231,58 @@ test bool localOverloadedFunction() = {0, 1, 2, 3} == testRenameOccurrences(" ' 'foo(h()); ", decls = "data D = g() | h();"); + +test bool functionsInVModuleStructureFromLeft() = testRenameOccurrences({ + byText("Left", "str foo(str s) = s when s == \"x\";", {0}), byText("Right", "str foo(str s) = s when s == \"y\";", {0}) + , byText("Merger", + "import Left; + 'import Right; + 'void main() { x = foo(\"foo\"); } + ", {0}) +}, <"Left", "foo", 0>); + +test bool functionsInVModuleStructureFromMerger() = testRenameOccurrences({ + byText("Left", "str foo(str s) = s when s == \"x\";", {0}), byText("Right", "str foo(str s) = s when s == \"y\";", {0}) + , byText("Merger", + "import Left; + 'import Right; + 'void main() { foo(\"foo\"); } + ", {0}) +}, <"Merger", "foo", 0>); + +test bool functionsInYModuleStructure() = testRenameOccurrences({ + byText("Left", "str foo(str s) = s when s == \"x\";", {0}), byText("Right", "str foo(str s) = s when s == \"x\";", {0}) + , byText("Merger", + "extend Left; + 'extend Right; + ", {}) + , byText("User", + "import Merger; + 'void main() { foo(\"foo\"); } + ", {0}) +}, <"Left", "foo", 0>); + +test bool functionsInInvertedVModuleStructure() = testRenameOccurrences({ + byText("Definer", "str foo(str s) = s when s == \"x\"; + 'str foo(str s) = s when s == \"y\";", {0, 1}), + byText("Left", "import Definer; + 'void main() { foo(\"foo\"); }", {0}), byText("Right", "import Definer; + 'void main() { foo(\"fu\"); }", {0}) +}, <"Left", "foo", 0>); + +test bool functionsInDiamondModuleStructure() = testRenameOccurrences({ + byText("Definer", "str foo(str s) = s when s == \"x\"; + 'str foo(str s) = s when s == \"y\";", {0, 1}), + byText("Left", "extend Definer;", {}), byText("Right", "extend Definer;", {}), + byText("User", "import Left; + 'import Right; + 'void main() { foo(\"foo\"); }", {0}) +}, <"Definer", "foo", 0>); + +test bool functionsInIIModuleStructure() = testRenameOccurrences({ + byText("LeftDefiner", "str foo(str s) = s when s == \"x\";", {0}), byText("RightDefiner", "str foo(str s) = s when s == \"y\";", {}), + byText("LeftExtender", "extend LeftDefiner;", {}), byText("RightExtender", "extend RightDefiner;", {}), + byText("LeftUser", "import LeftExtender; + 'void main() { foo(\"foo\"); }", {0}), byText("RightUser", "import RightExtender; + 'void main() { foo(\"fu\"); }", {}) +}, <"LeftDefiner", "foo", 0>); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index d7d26ee2e..1013e64e2 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -124,11 +124,13 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); + if (!ASSERT_EQ(expectedEditsPerModule, editsPerModule)) return false; + for (src <- pcfg.srcs) { verifyTypeCorrectRenaming(src, edits, pcfg); } - return ASSERT_EQ(editsPerModule, expectedEditsPerModule); + return true; } set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index fdad252a5..0e48ea8a7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -78,3 +78,105 @@ test bool multiModuleData() = testRenameOccurrences({ '}" , {1}) }, <"Main", "Bool", 1>, newName = "Boolean"); + +test bool dataTypesInVModuleStructureFromLeft() = testRenameOccurrences({ + byText("Left", "data Foo = f();", {0}), byText("Right", "data Foo = g();", {0}) + , byText("Merger", + "import Left; + 'import Right; + 'bool f(Foo foo) = (foo == f() || foo == g()); + ", {0}) +}, <"Left", "Foo", 0>, newName = "Bar"); + +test bool dataTypesInVModuleStructureFromMerger() = testRenameOccurrences({ + byText("Left", "data Foo = f();", {0}), byText("Right", "data Foo = g();", {0}) + , byText("Merger", + "import Left; + 'import Right; + 'bool f(Foo foo) = (foo == f() || foo == g()); + ", {0}) +}, <"Merger", "Foo", 0>, newName = "Bar"); + +@synopsis{ + (defs) + / \ + A B C + \/ \/ + D E + / \ + (uses) +} +test bool dataTypesInWModuleStructureWithoutMerge() = testRenameOccurrences({ + byText("A", "data Foo = f();", {0}), byText("B", "", {}), byText("C", "data Foo = h();", {}) + , byText("D", + "import A; + 'import B; + 'bool func(Foo foo) = foo == f(); + ", {0}), byText("E", + "import B; + 'import C; + 'bool func(Foo foo) = foo == h();", {}) +}, <"D", "Foo", 0>, newName = "Bar"); + +@synopsis{ + (defs) + / | \ + v v v + A B C + \/ \/ + D E + ^ ^ + / \ + (uses) +} +test bool dataTypesInWModuleStructureWithMerge() = testRenameOccurrences({ + byText("A", "data Foo = f();", {0}), byText("B", "data Foo = g();", {0}), byText("C", "data Foo = h();", {0}) + , byText("D", + "import A; + 'import B; + 'bool func(Foo foo) = foo == f(); + ", {0}), byText("E", + "import B; + 'import C; + 'bool func(Foo foo) = foo == h();", {0}) +}, <"D", "Foo", 0>, newName = "Bar"); + +test bool dataTypesInYModuleStructure() = testRenameOccurrences({ + byText("Left", "data Foo = f();", {0}), byText("Right", "data Foo = g();", {0}) + , byText("Merger", + "extend Left; + 'extend Right; + ", {}) + , byText("User", + "import Merger; + 'bool f(Foo foo) = (foo == f() || foo == g()); + ", {0}) +}, <"Left", "Foo", 0>, newName = "Bar"); + +test bool dataTypesInInvertedVModuleStructure() = testRenameOccurrences({ + byText("Definer", "data Foo = f() | g();", {0}), + byText("Left", "import Definer; + 'bool isF(Foo foo) = foo == f();", {0}), byText("Right", "import Definer; + 'bool isG(Foo foo) = foo == g();", {0}) +}, <"Left", "Foo", 0>, newName = "Bar"); + +test bool dataTypesInDiamondModuleStructure() = testRenameOccurrences({ + byText("Definer", "data Foo = f() | g();", {0}), + byText("Left", "extend Definer;", {}), byText("Right", "extend Definer;", {}), + byText("User", "import Left; + 'import Right; + 'bool isF(Foo foo) = foo == f(); + 'bool isG(Foo foo) = foo == g();", {0, 1}) +}, <"Definer", "Foo", 0>, newName = "Bar"); + +@synopsis{ + Two disjunct module trees. Both trees define `data Foo`. Since the trees are disjunct, + we expect a renaming triggered from the left side leaves the right side untouched. +} +test bool dataTypesInIIModuleStructure() = testRenameOccurrences({ + byText("LeftDefiner", "data Foo = f();", {0}), byText("RightDefiner", "data Foo = g();", {}), + byText("LeftExtender", "extend LeftDefiner;", {}), byText("RightExtender", "extend RightDefiner;", {}), + byText("LeftUser", "import LeftExtender; + 'bool func(Foo foo) = foo == f();", {0}), byText("RightUser", "import RightExtender; + 'bool func(Foo foo) = foo == g();", {}) +}, <"LeftDefiner", "Foo", 0>, newName = "Bar"); From b22fe3301083eed7909b294895bb99b302cf68c8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 16 Aug 2024 14:14:49 +0200 Subject: [PATCH 070/105] Improve/refactor collection field renaming. --- .../lang/rascal/lsp/refactor/Rename.rsc | 37 +++-- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 9 ++ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 142 +++++++++++------- .../lang/rascal/tests/rename/TestUtils.rsc | 6 +- 4 files changed, 126 insertions(+), 68 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 2578a5b63..e70ea8acf 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -259,6 +259,15 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { ws = preLoad(ws); + rel[loc field, loc container] fields = { + | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) + , just() := getFieldLocs(cursorName, t) + , loc fieldLoc <- fieldLocs + }; + + + Maybe[loc] smallestFieldContainingCursor = findSmallestContaining(fields.field, cursorLoc); + rel[loc l, CursorKind kind] locsContainingCursor = { | <- { @@ -268,10 +277,8 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , )[cursorName], cursorLoc), def()> // Type parameters , - // Collection field definitions; any location where the label equals the name under the cursor - , - // Collection field uses; any location which is of set or list type, where the label of the collection element equals the name under the cursor - , + // Any kind of field; we'll decide which exactly later + , // Module name declaration, where the cursor location is in the module header , } @@ -292,23 +299,22 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { cur = cursor(def(), c, cursorName); } case {use(), *_}: { - if (d <- ws.useDef[c], just(amodule(_)) := getFact(ws, d)) { + set[loc] defs = getDefs(ws, c); + set[Define] defines = {ws.definitions[d] | d <- defs, ws.definitions[d]?}; + + if (d <- defs, just(amodule(_)) := getFact(ws, d)) { // Cursor is at an import cur = cursor(moduleName(), c, cursorName); } else if (u <- ws.useDef<0>, u.begin <= cursorLoc.begin && u.end > cursorLoc.end) { // Cursor is at a qualified name cur = cursor(moduleName(), c, cursorName); - } else if (size(getDefs(ws, c) & ws.defines.defined) > 0) { + } else if (defines != {}) { // The cursor is at a use with corresponding definitions. cur = cursor(use(), c, cursorName); - } else if (just(at) := getFact(ws, c)) { - if (aparameter(cursorName, _) := at) { - // The cursor is at a type parameter - cur = cursor(typeParam(), c, cursorName); - } else if (at.alabel == cursorName) { - // The cursor is at a collection field - cur = cursor(collectionField(), c, cursorName); - } + } else if (just(at) := getFact(ws, c) + , aparameter(cursorName, _) := at) { + // The cursor is at a type parameter + cur = cursor(typeParam(), c, cursorName); } } case {k}: { @@ -386,7 +392,10 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s step("loading required type information", 1); if (!rascalIsFunctionLocal(ws, cur)) { + println("Renaming not module-local; loading more information from workspace."); ws = loadWorkspace(ws); + } else { + println("Renaming guaranteed to be module-local."); } step("collecting uses of \'\'", 1); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 46d4cc443..5de680138 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -82,3 +82,12 @@ str toString(map[str, list[Message]] moduleMsgs) = rel[&K, &V] groupBy(set[&V] s, &K(&V) pred) = { | v <- s}; + +bool byLength(loc l1, loc l2) = l1.length < l2.length; +bool byOffset(loc l1, loc l2) = l1.offset < l2.offset; + +bool(&T, &T) desc(bool(&T, &T) f) { + return bool(&T t1, &T t2) { + return f(t2, t1); + }; +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index cc5b17133..f21356dde 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -193,6 +193,8 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv private rel[loc, loc] NO_RENAMES(str _) = {}; private int qualSepSize = size("::"); +bool isCollectionType(AType at) = at is arel || at is alrel || at is atuple; + set[loc] getKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = {}; @@ -211,9 +213,37 @@ set[loc] getKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { return uses; } +set[loc] getKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { + set[loc] uses = getUses(ws, defs); + + for (d <- defs + , Define dataDef: <_, _, _, dataId(), _, _> <- ws.defines + , isStrictlyContainedIn(d, dataDef.defined) + , Define consDef: <_, _, _, constructorId(), _, _> <- ws.defines + , isStrictlyContainedIn(consDef.defined, dataDef.defined) + ) { + if (AType fieldType := ws.definitions[d].defInfo.atype) { + // println("Def: "); + if (<{}, consUses, _> := getFieldDefsUses(ws, dataDef.defInfo.atype, fieldType, cursorName)) { + uses += consUses; + } + } else { + throw unsupportedRename("Unknown type for definition at : "); + } + } + + return uses; +} + DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { defs = getOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); - uses = getUses(ws, defs) + getKeywordFormalUses(ws, defs, cursorName); + uses = getUses(ws, defs); + + if (keywordFormalId() in {ws.definitions[d].idRole | d <- defs}) { + uses += getKeywordFormalUses(ws, defs, cursorName); + uses += getKeywordFieldUses(ws, defs, cursorName); + } + return ; } @@ -221,7 +251,12 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayO set[loc] initialUses = getUses(ws, l); set[loc] initialDefs = {l} + {*ds | u <- initialUses, ds := getDefs(ws, u)}; defs = getOverloadedDefs(ws, initialDefs, mayOverloadF); - uses = getUses(ws, defs) + getKeywordFormalUses(ws, defs, cursorName); + uses = getUses(ws, defs); + + if (keywordFormalId() in {ws.definitions[d].idRole | d <- defs}) { + uses += getKeywordFormalUses(ws, defs, cursorName); + uses += getKeywordFieldUses(ws, defs, cursorName); + } return ; } @@ -257,67 +292,68 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cur } DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { + bool isTupleField(AType fieldType) = fieldType.alabel == ""; + AType cursorType = ws.facts[cursorLoc]; - factLocsSortedBySize = sort(domain(ws.facts), bool(loc l1, loc l2) { return l1.length < l2.length; }); - - AType fieldType = avoid(); - AType collectionType = avoid(); - if (l <- factLocsSortedBySize, isStrictlyContainedIn(cursorLoc, l), at := ws.facts[l], (at is arel || at is alrel || at is atuple), at.elemType is atypeList) { - // We are at a definition site - collectionType = at; - fieldType = cursorType; + list[loc] factLocsSortedBySize = sort(domain(ws.facts), byLength); + + if (l <- factLocsSortedBySize, isStrictlyContainedIn(cursorLoc, l), collUseType := ws.facts[l], isCollectionType(collUseType), collUseType.elemType is atypeList) { + // We are at a collection definition site + return getFieldDefsUses(ws, collUseType, cursorType, cursorName); } - else { + + // We can find the collection type by looking for the first use to the left of the cursor that has a collection type + lrel[loc use, loc def] usesToLeft = reverse(sort({ | <- ws.useDef, isSameFile(u, cursorLoc), u.offset < cursorLoc.offset})); + if (<_, d> <- usesToLeft, define := ws.definitions[d], defType(AType collDefType) := define.defInfo, isCollectionType(collDefType)) { // We are at a use site, where the field element type is wrapped in a `aset` of `alist` constructor - fieldType = cursorType.alabel == "" - // Collection type - ? cursorType.elmType - // Tuple type - : cursorType; - - // We need to find the collection type by looking for the first use to the left of the cursor that has a collection type - usesToLeft = reverse(sort({ | <- ws.useDef, isSameFile(u, cursorLoc), u.offset < cursorLoc.offset})); - if (<_, d> <- usesToLeft, define := ws.definitions[d], defType(AType at) := define.defInfo, (at is arel || at is alrel || at is atuple)) { - collectionType = at; - } else { - throw unsupportedRename("Could not find a collection definition corresponding to the field at the cursor."); - } + return getFieldDefsUses(ws, collDefType, isTupleField(cursorType) ? cursorType.elmType : cursorType, cursorName); + } else { + throw unsupportedRename("Could not find a collection definition corresponding to the field at the cursor."); } +} + +Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `.`) = + just() when fieldName == ""; + +Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Assignable) `.`) = + just() when fieldName == ""; + +Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `\< <{Field ","}+ fields> \>`) { + fieldLocs = {field.src + | field <- fields + , field is name + , "" == fieldName + }; + + return fieldLocs != {} ? just() : nothing(); +} + +Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `[ = ]`) = + just() when fieldName == ""; + +Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (StructuredType) `[<{TypeArg ","}+ args>]`) { + fieldLocs = {name.src | (TypeArg) ` ` <- args, fieldName == ""}; + return fieldLocs != {} ? just() : nothing(); +} + +default Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, Tree _) = nothing(); - set[loc] collectionFacts = factsInvert(ws)[collectionType]; - rel[loc file, loc u] factsByModule = groupBy(collectionFacts, loc(loc l) { return l.top; }); +private DefsUsesRenames getFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { + set[loc] containerFacts = factsInvert(ws)[containerType]; + rel[loc file, loc u] factsByModule = groupBy(containerFacts, loc(loc l) { return l.top; }); set[loc] defs = {}; set[loc] uses = {}; for (file <- factsByModule.file) { fileFacts = factsByModule[file]; - visit(parseModuleWithSpacesCached(file)) { - case (Expression) `.`: { - if ("" == cursorName && any(f <- fileFacts, isContainedIn(f, e.src))) { - uses += field.src; - } - } - case (Assignable) `.`: { - if ("" == cursorName && any(f <- fileFacts, isContainedIn(f, rec.src))) { - uses += field.src; - } - } - case (Expression) `\< <{Field ","}+ fields> \>`: { - if (any(f <- fileFacts, isContainedIn(e.src, f))) { - uses += {field.src | field <- fields - , field is name - , "" == cursorName}; - } - } - case (Expression) `[ = ]`: { - if ("" == cursorName && any(f <- fileFacts, isContainedIn(f, e.src))) { - uses += field.src; - } - } - case t:(StructuredType) `[<{TypeArg ","}+ args>]`: { - for (at := ws.facts[t.src], collectionType.elemType == at.elemType, (TypeArg) ` ` <- args, fieldType == ws.facts[name.src]) { - defs += name.src; - } + for (/Tree t := parseModuleWithSpacesCached(file), just() := getFieldLocs(cursorName, t)) { + if ((StructuredType) `[<{TypeArg ","}+ _>]` := t + , at := ws.facts[t.src] + , containerType.elemType? + , containerType.elemType == at.elemType) { + defs += {f | f <- fields, fieldType == ws.facts[f]}; + } else if (any(f <- fileFacts, isContainedIn(f, lhs))) { + uses += fields; } } } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 1013e64e2..717f3de9d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -251,7 +251,11 @@ private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] } if ([changed(_, replaces)] := edits) { - idx = {indexOf(oldNameOccurrences, l) | replace(l, _) <- replaces}; + set[int] idx = {}; + for (replace(replaceAt, replaceWith) <- replaces) { + if (oldText := readFile(replaceAt), "" != name) throw "Unexpected change for \'\' at "; + idx += indexOf(oldNameOccurrences, replaceAt); + } return idx; } else { print("Unexpected changes: "); From 680f863c37cae50f24d079334e75b8ae610681a6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 12:25:50 +0200 Subject: [PATCH 071/105] Temporarily remove some tests until ADTs have been implemented fully. --- .../lang/rascal/tests/rename/Fields.rsc | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 88f871e00..4d9344925 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -41,11 +41,12 @@ test bool dataFieldAtUse() = {0, 1} == testRenameOccurrences(" ", decls = "data D = d(int foo, int baz);" , cursorAtOldNameOccurrence = 1); -test bool multipleConstructorDataField() = {0, 1, 2} == testRenameOccurrences(" - 'x = d(1, 2); - 'y = x.foo; - ", decls = "data D = d(int foo) | d(int foo, int baz);" -, cursorAtOldNameOccurrence = 1); +// TODO Implement data types properly +// test bool multipleConstructorDataField() = {0, 1, 2} == testRenameOccurrences(" +// 'x = d(1, 2); +// 'y = x.foo; +// ", decls = "data D = d(int foo) | d(int foo, int baz);" +// , cursorAtOldNameOccurrence = 1); @expected{illegalRename} test bool duplicateDataField() = testRename("", decls = @@ -74,15 +75,16 @@ test bool crossModuleDataFieldAtUse() = testRenameOccurrences({ ", {0}) }, <"Main", "foo", 0>); -test bool extendedDataField() = testRenameOccurrences({ - byText("Scratch1", " - 'data Foo = f(int foo); - ", {0}), - byText("Scratch2", " - 'extend Scratch1; - 'data Foo = g(int foo); - ", {0}) -}, <"Scratch2", "foo", 0>); +// TODO Implement data types properly +// test bool extendedDataField() = testRenameOccurrences({ +// byText("Scratch1", " +// 'data Foo = f(int foo); +// ", {0}), +// byText("Scratch2", " +// 'extend Scratch1; +// 'data Foo = g(int foo); +// ", {0}) +// }, <"Scratch2", "foo", 0>); test bool relFieldAtDef() = {0, 1} == testRenameOccurrences(" 'rel[str foo, str baz] r = {}; From 7fe3192c4197ff87d406e8ab157ece1095d95c17 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 15:56:28 +0200 Subject: [PATCH 072/105] Rename Rascal speicifc functions. --- .../lang/rascal/lsp/refactor/Rename.rsc | 16 ++--- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 60 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index e70ea8acf..758addda1 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -245,9 +245,9 @@ private bool rascalIsFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { } private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(def(), cursorLoc, _)) = - rascalIsFunctionLocalDefs(ws, getOverloadedDefs(ws, {cursorLoc}, rascalMayOverloadSameName)); + rascalIsFunctionLocalDefs(ws, rascalGetOverloadedDefs(ws, {cursorLoc}, rascalMayOverloadSameName)); private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)) = - rascalIsFunctionLocalDefs(ws, getOverloadedDefs(ws, getDefs(ws, cursorLoc), rascalMayOverloadSameName)); + rascalIsFunctionLocalDefs(ws, rascalGetOverloadedDefs(ws, getDefs(ws, cursorLoc), rascalMayOverloadSameName)); private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(collectionField(), _, _)) = false; private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(moduleName(), _, _)) = false; @@ -261,7 +261,7 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { rel[loc field, loc container] fields = { | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) - , just() := getFieldLocs(cursorName, t) + , just() := rascalGetFieldLocs(cursorName, t) , loc fieldLoc <- fieldLocs }; @@ -332,9 +332,7 @@ private set[Name] rascalNameToEquivalentNames(str name) = { startsWith(name, "\\") ? [Name] name : [Name] "\\" }; -private bool containsName(loc l, str name) { - // If we do not find any occurrences of the name under the cursor in a module, - // we are not interested in it at all, and will skip loading its TPL. +private bool rascalContainsName(loc l, str name) { m = parseModuleWithSpacesCached(l); for (n <- rascalNameToEquivalentNames(name)) { if (/n := m) return true; @@ -364,7 +362,9 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s , srcFolder <- pcfg.srcs , file <- find(srcFolder, "rsc") , file != cursorLoc.top // because we loaded that during preload - , containsName(file, cursorName) + // If we do not find any occurrences of the name under the cursor in a module, + // we are not interested in it at all, and will skip loading its TPL. + , rascalContainsName(file, cursorName) }; }, // Load TModel for loc @@ -399,7 +399,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s } step("collecting uses of \'\'", 1); - = getDefsUses(ws, cur, rascalMayOverloadSameName, getPathConfig); + = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName, getPathConfig); rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index f21356dde..d9b611e7a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -151,7 +151,7 @@ set[loc] getModuleScopes(WorkspaceInfo ws) = invert(ws.scopes)[|global-scope:/// map[loc, loc] getModuleScopePerFile(WorkspaceInfo ws) = (scope.top: scope | loc scope <- getModuleScopes(ws)); @memo{maximumSize=1, minutes=5} -rel[loc from, loc to] getTransitiveReflexiveModulePaths(WorkspaceInfo ws) { +rel[loc from, loc to] rascalGetTransitiveReflexiveModulePaths(WorkspaceInfo ws) { rel[loc from, loc to] moduleI = ident(getModuleScopes(ws)); rel[loc from, loc to] imports = (ws.paths)[importPath()]; rel[loc from, loc to] extends = (ws.paths)[extendPath()]; @@ -161,7 +161,7 @@ rel[loc from, loc to] getTransitiveReflexiveModulePaths(WorkspaceInfo ws) { ; } -set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { +set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { set[loc] overloadedDefs = defs; // Pre-condition @@ -171,7 +171,7 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv map[loc file, loc scope] moduleScopePerFile = getModuleScopePerFile(ws); rel[loc d, loc scope] scopesOfDefUses = { | <- ws.useDef}; rel[loc def, loc scope] defAndTheirUseScopes = ws.defines + scopesOfDefUses; - rel[loc from, loc to] modulePaths = getTransitiveReflexiveModulePaths(ws); + rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); solve(overloadedDefs) { rel[loc from, loc to] reachableDefs = @@ -193,9 +193,9 @@ set[loc] getOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOv private rel[loc, loc] NO_RENAMES(str _) = {}; private int qualSepSize = size("::"); -bool isCollectionType(AType at) = at is arel || at is alrel || at is atuple; +bool rascalIsCollectionType(AType at) = at is arel || at is alrel || at is atuple; -set[loc] getKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { +set[loc] rascalGetKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = {}; for (d <- defs @@ -213,7 +213,7 @@ set[loc] getKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { return uses; } -set[loc] getKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { +set[loc] rascalGetKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = getUses(ws, defs); for (d <- defs @@ -224,7 +224,7 @@ set[loc] getKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { ) { if (AType fieldType := ws.definitions[d].defInfo.atype) { // println("Def: "); - if (<{}, consUses, _> := getFieldDefsUses(ws, dataDef.defInfo.atype, fieldType, cursorName)) { + if (<{}, consUses, _> := rascalGetFieldDefsUses(ws, dataDef.defInfo.atype, fieldType, cursorName)) { uses += consUses; } } else { @@ -235,33 +235,33 @@ set[loc] getKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { return uses; } -DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { - defs = getOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { + defs = rascalGetOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); uses = getUses(ws, defs); if (keywordFormalId() in {ws.definitions[d].idRole | d <- defs}) { - uses += getKeywordFormalUses(ws, defs, cursorName); - uses += getKeywordFieldUses(ws, defs, cursorName); + uses += rascalGetKeywordFormalUses(ws, defs, cursorName); + uses += rascalGetKeywordFieldUses(ws, defs, cursorName); } return ; } -DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { set[loc] initialUses = getUses(ws, l); set[loc] initialDefs = {l} + {*ds | u <- initialUses, ds := getDefs(ws, u)}; - defs = getOverloadedDefs(ws, initialDefs, mayOverloadF); + defs = rascalGetOverloadedDefs(ws, initialDefs, mayOverloadF); uses = getUses(ws, defs); if (keywordFormalId() in {ws.definitions[d].idRole | d <- defs}) { - uses += getKeywordFormalUses(ws, defs, cursorName); - uses += getKeywordFieldUses(ws, defs, cursorName); + uses += rascalGetKeywordFormalUses(ws, defs, cursorName); + uses += rascalGetKeywordFieldUses(ws, defs, cursorName); } return ; } -DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { AType at = ws.facts[cursorLoc]; if(Define d: <_, _, _, _, _, defType(afunc(_, /at, _))> <- ws.defines, isContainedIn(cursorLoc, d.defined)) { // From here on, we can assume that all locations are in the same file, because we are dealing with type parameters and filtered on `isContainedIn` @@ -291,34 +291,34 @@ DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cur throw unsupportedRename("Cannot find function definition which defines template variable \'\'"); } -DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { bool isTupleField(AType fieldType) = fieldType.alabel == ""; AType cursorType = ws.facts[cursorLoc]; list[loc] factLocsSortedBySize = sort(domain(ws.facts), byLength); - if (l <- factLocsSortedBySize, isStrictlyContainedIn(cursorLoc, l), collUseType := ws.facts[l], isCollectionType(collUseType), collUseType.elemType is atypeList) { + if (l <- factLocsSortedBySize, isStrictlyContainedIn(cursorLoc, l), collUseType := ws.facts[l], rascalIsCollectionType(collUseType), collUseType.elemType is atypeList) { // We are at a collection definition site - return getFieldDefsUses(ws, collUseType, cursorType, cursorName); + return rascalGetFieldDefsUses(ws, collUseType, cursorType, cursorName); } // We can find the collection type by looking for the first use to the left of the cursor that has a collection type lrel[loc use, loc def] usesToLeft = reverse(sort({ | <- ws.useDef, isSameFile(u, cursorLoc), u.offset < cursorLoc.offset})); - if (<_, d> <- usesToLeft, define := ws.definitions[d], defType(AType collDefType) := define.defInfo, isCollectionType(collDefType)) { + if (<_, d> <- usesToLeft, define := ws.definitions[d], defType(AType collDefType) := define.defInfo, rascalIsCollectionType(collDefType)) { // We are at a use site, where the field element type is wrapped in a `aset` of `alist` constructor - return getFieldDefsUses(ws, collDefType, isTupleField(cursorType) ? cursorType.elmType : cursorType, cursorName); + return rascalGetFieldDefsUses(ws, collDefType, isTupleField(cursorType) ? cursorType.elmType : cursorType, cursorName); } else { throw unsupportedRename("Could not find a collection definition corresponding to the field at the cursor."); } } -Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `.`) = +Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Expression) `.`) = just() when fieldName == ""; -Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Assignable) `.`) = +Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Assignable) `.`) = just() when fieldName == ""; -Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `\< <{Field ","}+ fields> \>`) { +Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Expression) `\< <{Field ","}+ fields> \>`) { fieldLocs = {field.src | field <- fields , field is name @@ -328,17 +328,17 @@ Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `) : nothing(); } -Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (Expression) `[ = ]`) = +Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Expression) `[ = ]`) = just() when fieldName == ""; -Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, (StructuredType) `[<{TypeArg ","}+ args>]`) { +Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (StructuredType) `[<{TypeArg ","}+ args>]`) { fieldLocs = {name.src | (TypeArg) ` ` <- args, fieldName == ""}; return fieldLocs != {} ? just() : nothing(); } -default Maybe[tuple[loc, set[loc]]] getFieldLocs(str fieldName, Tree _) = nothing(); +default Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, Tree _) = nothing(); -private DefsUsesRenames getFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { +private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { set[loc] containerFacts = factsInvert(ws)[containerType]; rel[loc file, loc u] factsByModule = groupBy(containerFacts, loc(loc l) { return l.top; }); @@ -346,7 +346,7 @@ private DefsUsesRenames getFieldDefsUses(WorkspaceInfo ws, AType containerType, set[loc] uses = {}; for (file <- factsByModule.file) { fileFacts = factsByModule[file]; - for (/Tree t := parseModuleWithSpacesCached(file), just() := getFieldLocs(cursorName, t)) { + for (/Tree t := parseModuleWithSpacesCached(file), just() := rascalGetFieldLocs(cursorName, t)) { if ((StructuredType) `[<{TypeArg ","}+ _>]` := t , at := ws.facts[t.src] , containerType.elemType? @@ -361,7 +361,7 @@ private DefsUsesRenames getFieldDefsUses(WorkspaceInfo ws, AType containerType, return ; } -DefsUsesRenames getDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) getPathConfig) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) getPathConfig) { loc moduleFile = |unknown:///|; if (d <- ws.useDef[cursorLoc], amodule(_) := ws.facts[d]) { // Cursor is at an import From 9caab3362216cd2c96f2e6c2501ed1888ff29cc4 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 15:56:55 +0200 Subject: [PATCH 073/105] Fix memoization retention policies. --- .../rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index d9b611e7a..3be7152a8 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -130,10 +130,10 @@ loc getProjectFolder(WorkspaceInfo ws, loc l) { throw "Could not find project containing "; } -@memo{maximumSize=1, minutes=5} +@memo{maximumSize(1), expireAfter(minutes=(5))} rel[loc, loc] defUse(WorkspaceInfo ws) = invert(ws.useDef); -@memo{maximumSize=1, minutes=5} +@memo{maximumSize(1), expireAfter(minutes=(5))} map[AType, set[loc]] factsInvert(WorkspaceInfo ws) = invert(ws.facts); set[loc] getUses(WorkspaceInfo ws, loc def) = defUse(ws)[def]; @@ -144,13 +144,13 @@ set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; Maybe[AType] getFact(WorkspaceInfo ws, loc l) = l in ws.facts ? just(ws.facts[l]) : nothing(); -@memo{maximumSize=1, minutes=5} +@memo{maximumSize(1), expireAfter(minutes=(5))} set[loc] getModuleScopes(WorkspaceInfo ws) = invert(ws.scopes)[|global-scope:///|]; -@memo{maximumSize=1, minutes=5} +@memo{maximumSize(1), expireAfter(minutes=(5))} map[loc, loc] getModuleScopePerFile(WorkspaceInfo ws) = (scope.top: scope | loc scope <- getModuleScopes(ws)); -@memo{maximumSize=1, minutes=5} +@memo{maximumSize(1), expireAfter(minutes=(5))} rel[loc from, loc to] rascalGetTransitiveReflexiveModulePaths(WorkspaceInfo ws) { rel[loc from, loc to] moduleI = ident(getModuleScopes(ws)); rel[loc from, loc to] imports = (ws.paths)[importPath()]; From 3a3ecb89ba5f7afba40ca5cf14b483c75431d0d0 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 16:36:21 +0200 Subject: [PATCH 074/105] Document renaming and utility functions. --- .../lang/rascal/lsp/refactor/Rename.rsc | 45 +++++++++++++++++++ .../rascal/lang/rascal/lsp/refactor/Util.rsc | 27 ++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 758addda1..10ae25971 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -340,6 +340,51 @@ private bool rascalContainsName(loc l, str name) { return false; } +@synopsis{ + Rename the Rascal symbol under the cursor. Renames all related (overloaded) definitions and uses of those definitions. + Renaming is not supported for some symbols. +} +@description { + Rename the Rascal symbol under the cursor, across all currently open projects in the workspace. + The following symbols are supported. + - Variables + - Pattern variables + - Parameters (positional, keyword) + - Functions + - Annotations (on values) + - Collection fields (tuple, relations) + - Modules + - Aliases + - Data types + - Type parameters + + The following symbols are currently unsupported. + - Annotations (on functions) + - Data constructors + - Data constructor fields (fields, keyword fields, common keyword fields) + + *Name resolution* + A renaming triggers the typechecker on the currently open file to determine the scope of the renaming. + If the renaming is not function-local, it might trigger the type checker on all files in the workspace to find rename candidates. + A renaming requires all files in which the name is used to be without errors. + + *Overloading* + Considers recognizes overloaded definitions and renames those as well. + + Functions will be considered overloaded when they have the same name, even when the arity or type signature differ. + This means that the following functions defitions will be renamed in unison: + ``` + list[&T] concat(list[&T] _, list[&T] _) = _; + set[&T] concat(set[&T] _, set[&T] _) = _; + set[&T] concat(set[&T] _, set[&T] _, set[&T] _) = _; + ``` + + *Validity checking* + Once all rename candidates have been resolved, validity of the renaming will be checked. A rename is valid iff + 1. It does not introduce errors. + 2. It does not change the semantics of the application. + 3. It does not change definitions outside of the current workspace. +} list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) = job("renaming to ", list[DocumentEdit](void(str, int) step) { loc cursorLoc = cursorT.src; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index 5de680138..bb5689657 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -37,9 +37,13 @@ import util::Reflective; import lang::rascal::\syntax::Rascal; -Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) { +@synopsis{ + Finds the smallest location in `wrappers` than contains `l`. If none contains `l`, returns `nothing().` + Accepts a predicate deciding containment as an optional argument. +} +Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l, bool(loc, loc) containmentPred = isContainedIn) { Maybe[loc] result = nothing(); - for (w <- wrappers, isContainedIn(l, w)) { + for (w <- wrappers, containmentPred(l, w)) { switch (result) { case just(loc current): if (w < current) result = just(w); case nothing(): result = just(w); @@ -48,6 +52,9 @@ Maybe[loc] findSmallestContaining(set[loc] wrappers, loc l) { return result; } +@synopsis{ + Resizes a location by removing a prefix and/or suffix. +} loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { assert l.begin.line == l.end.line : "Cannot trim a multi-line location"; @@ -57,10 +64,16 @@ loc trim(loc l, int removePrefix = 0, int removeSuffix = 0) { [end = ]; } +@synopsis{ + Decides if `prefix` is a prefix of `l`. +} bool isPrefixOf(loc prefix, loc l) = l.scheme == prefix.scheme && l.authority == prefix.authority && startsWith(l.path, endsWith(prefix.path, "/") ? prefix.path : prefix.path + "/"); +@synopsis{ + A cached wrapper for the Rascal whole-module parse function. +} start[Module] parseModuleWithSpacesCached(loc l) { @memo{expireAfter(minutes=5)} start[Module] parseModuleWithSpacesCached(loc l, datetime _) = parseModuleWithSpaces(l); return parseModuleWithSpacesCached(l, lastModified(l)); @@ -83,9 +96,19 @@ str toString(map[str, list[Message]] moduleMsgs) = rel[&K, &V] groupBy(set[&V] s, &K(&V) pred) = { | v <- s}; +@synopsis{ + Predicate to sort locations by length. +} bool byLength(loc l1, loc l2) = l1.length < l2.length; + +@synopsis{ + Predicate to sort locations by offset. +} bool byOffset(loc l1, loc l2) = l1.offset < l2.offset; +@synopsis{ + Predicate to reverse a sort order. +} bool(&T, &T) desc(bool(&T, &T) f) { return bool(&T t1, &T t2) { return f(t2, t1); From 566a922725617aab2c676376803d8edcc7d701ff Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 16:59:49 +0200 Subject: [PATCH 075/105] Rename test comparison helper. --- .../main/rascal/lang/rascal/tests/rename/FormalParameters.rsc | 4 ++-- .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc index 2cb8f4854..2fb78bd0a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc @@ -70,12 +70,12 @@ test bool nestedKeywordParameter() = {0, 1, 2} == testRenameOccurrences(" 'int x = f(foo = 10); "); -test bool keywordParameterFromDef() = ASSERT_EQ({0, 1, 2}, testRenameOccurrences( +test bool keywordParameterFromDef() = expectEq({0, 1, 2}, testRenameOccurrences( "int x = f(foo = 10);" , decls="int f(int foo = 8) = foo;" )); -test bool keywordParameterFromUse() = ASSERT_EQ({0, 1, 2}, testRenameOccurrences( +test bool keywordParameterFromUse() = expectEq({0, 1, 2}, testRenameOccurrences( "int x = f(foo = 10);" , decls="int f(int foo = 8) = foo;" , cursorAtOldNameOccurrence = 1 diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 717f3de9d..034a15b0f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -67,7 +67,7 @@ private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathC throwAnyErrors(checkAll(root, ccfg)); } -bool ASSERT_EQ(&T expected, &T actual) { +bool expectEq(&T expected, &T actual) { if (expected != actual) { print("EXPECTED: "); iprintln(expected); @@ -124,7 +124,7 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); - if (!ASSERT_EQ(expectedEditsPerModule, editsPerModule)) return false; + if (!expectEq(expectedEditsPerModule, editsPerModule)) return false; for (src <- pcfg.srcs) { verifyTypeCorrectRenaming(src, edits, pcfg); From 2e46c7e0001c7aa209b3693995b28836c09a645c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 20 Aug 2024 15:48:08 +0200 Subject: [PATCH 076/105] Remove now-unused file. --- .../lang/rascal/lsp/refactor/Benchmark.rsc | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc deleted file mode 100644 index 64e334800..000000000 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Benchmark.rsc +++ /dev/null @@ -1,83 +0,0 @@ -@license{ -Copyright (c) 2018-2023, NWO-I CWI and 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. -} -@bootstrapParser - -module lang::rascal::lsp::refactor::Benchmark - -import IO; -import Set; -import lang::rascal::\syntax::Rascal; - -import util::Benchmark; -import util::FileSystem; -import util::Reflective; - -void benchmarkDeepTreeMatch(loc projectPath) { - name = "val"; - - println("Collecting files..."); - fs = find(projectPath, "rsc"); - - println("Parsing modules..."); - trees = {parseModuleWithSpaces(f) | f <- fs}; - - println("Benchmarking trees"); - iprintln(benchmark(( - "Deep match (once)": void() { deepMatchOnce(trees, name); }, - "Deep match (twice)": void() { deepMatchTwice(trees, name); } - ))); -} - -void deepMatchTwice(set[start[Module]] trees, str name) { - int matches = 0; - - reg = [Name] name; - esc = [Name] "\\"; - - for (tree <- trees) { - if (/reg := tree) { - matches += 1; - } - else if (/esc := tree) { - matches += 1; - } - } - println("[Twice] # of matches: "); -} -void deepMatchOnce(set[start[Module]] trees, str name) { - int matches = 0; - - reg = [Name] name; - esc = [Name] "\\"; - - for (tree <- trees) { - if (/Name n := tree, n := reg || n := esc) { - matches += 1; - } - } - println("[Once] # of matches: "); -} From 7c03ee29dfb306e601f0edc2fa8119bb51b933d6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 20 Aug 2024 16:05:15 +0200 Subject: [PATCH 077/105] Small fixes from review. --- .../main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 2 -- .../rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 10ae25971..266e2f8af 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -416,8 +416,6 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s set[TModel](ProjectFiles projectFiles) { set[TModel] tmodels = {}; - if (projectFiles == {}) return tmodels; - for (projectFolder <- projectFiles.projectFolder, \files := projectFiles[projectFolder]) { PathConfig pcfg = getPathConfig(projectFolder); RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true] diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 3be7152a8..ab83ae965 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -130,10 +130,10 @@ loc getProjectFolder(WorkspaceInfo ws, loc l) { throw "Could not find project containing "; } -@memo{maximumSize(1), expireAfter(minutes=(5))} +@memo{maximumSize(1), expireAfter(minutes=5)} rel[loc, loc] defUse(WorkspaceInfo ws) = invert(ws.useDef); -@memo{maximumSize(1), expireAfter(minutes=(5))} +@memo{maximumSize(1), expireAfter(minutes=5)} map[AType, set[loc]] factsInvert(WorkspaceInfo ws) = invert(ws.facts); set[loc] getUses(WorkspaceInfo ws, loc def) = defUse(ws)[def]; @@ -144,13 +144,13 @@ set[loc] getDefs(WorkspaceInfo ws, loc use) = ws.useDef[use]; Maybe[AType] getFact(WorkspaceInfo ws, loc l) = l in ws.facts ? just(ws.facts[l]) : nothing(); -@memo{maximumSize(1), expireAfter(minutes=(5))} +@memo{maximumSize(1), expireAfter(minutes=5)} set[loc] getModuleScopes(WorkspaceInfo ws) = invert(ws.scopes)[|global-scope:///|]; -@memo{maximumSize(1), expireAfter(minutes=(5))} +@memo{maximumSize(1), expireAfter(minutes=5)} map[loc, loc] getModuleScopePerFile(WorkspaceInfo ws) = (scope.top: scope | loc scope <- getModuleScopes(ws)); -@memo{maximumSize(1), expireAfter(minutes=(5))} +@memo{maximumSize(1), expireAfter(minutes=5)} rel[loc from, loc to] rascalGetTransitiveReflexiveModulePaths(WorkspaceInfo ws) { rel[loc from, loc to] moduleI = ident(getModuleScopes(ws)); rel[loc from, loc to] imports = (ws.paths)[importPath()]; From 3046dd58519075a362586284c3130cbdac870daf Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 18:20:46 +0200 Subject: [PATCH 078/105] Run single-module tests with all expected occurrences as cursor. --- .../lang/rascal/tests/rename/Annotations.rsc | 21 +---- .../lang/rascal/tests/rename/Fields.rsc | 54 +++---------- .../rascal/tests/rename/FormalParameters.rsc | 35 ++++---- .../lang/rascal/tests/rename/Functions.rsc | 80 ++++++------------- .../lang/rascal/tests/rename/Performance.rsc | 6 +- .../lang/rascal/tests/rename/TestUtils.rsc | 18 ++++- .../rascal/lang/rascal/tests/rename/Types.rsc | 10 +-- .../lang/rascal/tests/rename/Variables.rsc | 18 ++--- 8 files changed, 86 insertions(+), 156 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc index 24c7f6214..e64f9a32f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Annotations.rsc @@ -29,32 +29,17 @@ module lang::rascal::tests::rename::Annotations import lang::rascal::tests::rename::TestUtils; import lang::rascal::lsp::refactor::Exception; -test bool annoAtDecl() = {0, 1} == testRenameOccurrences(" +test bool userDefinedAnno() = testRenameOccurrences({0, 1, 2}, " 'x = d(); 'x@foo = 8; - ", decls = " - 'data D = d(); - 'anno int D@foo; -"); - -test bool annoAtUse() = {0, 1} == testRenameOccurrences(" - 'x = d(); 'int i = x@foo; ", decls = " 'data D = d(); 'anno int D@foo; -", cursorAtOldNameOccurrence = 1); - -test bool annoAtAssign() = {0, 1} == testRenameOccurrences(" - 'x = d(); - 'x@foo = 8; - ", decls = " - 'data D = d(); - 'anno int D@foo; -", cursorAtOldNameOccurrence = 1); +"); @expected{illegalRename} -test bool builtinAnno() = {0, 1} == testRenameOccurrences(" +test bool builtinAnno() = testRename(" 'Tree t = char(8); 'x = t@\\loc; ", oldName = "\\loc", imports = "import ParseTree;"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 4d9344925..a598aafdd 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -29,20 +29,14 @@ module lang::rascal::tests::rename::Fields import lang::rascal::tests::rename::TestUtils; import lang::rascal::lsp::refactor::Exception; -test bool dataFieldAtDef() = {0, 1} == testRenameOccurrences(" +test bool dataField() = testRenameOccurrences({0, 1}, " 'D oneTwo = d(1, 2); 'x = d.foo; ", decls = "data D = d(int foo, int baz);" ); -test bool dataFieldAtUse() = {0, 1} == testRenameOccurrences(" - 'D oneTwo = d(1, 2); - 'x = d.foo; - ", decls = "data D = d(int foo, int baz);" -, cursorAtOldNameOccurrence = 1); - // TODO Implement data types properly -// test bool multipleConstructorDataField() = {0, 1, 2} == testRenameOccurrences(" +// test bool multipleConstructorDataField() = testRenameOccurrences({0, 1, 2}, " // 'x = d(1, 2); // 'y = x.foo; // ", decls = "data D = d(int foo) | d(int foo, int baz);" @@ -86,49 +80,39 @@ test bool crossModuleDataFieldAtUse() = testRenameOccurrences({ // ", {0}) // }, <"Scratch2", "foo", 0>); -test bool relFieldAtDef() = {0, 1} == testRenameOccurrences(" +test bool relField() = testRenameOccurrences({0, 1}, " 'rel[str foo, str baz] r = {}; 'f = r.foo; "); -test bool relFieldAtUse() = {0, 1} == testRenameOccurrences(" - 'rel[str foo, str baz] r = {}; - 'f = r.foo; -", cursorAtOldNameOccurrence = 1); - -test bool lrelFieldAtDef() = {0, 1} == testRenameOccurrences(" +test bool lrelField() = testRenameOccurrences({0, 1}, " 'lrel[str foo, str baz] r = []; 'f = r.foo; "); -test bool lrelFieldAtUse() = {0, 1} == testRenameOccurrences(" - 'lrel[str foo, str baz] r = []; - 'foos = r.foo; -", cursorAtOldNameOccurrence = 1); - -test bool relSubscript() = {0, 1} == testRenameOccurrences(" +test bool relSubscript() = testRenameOccurrences({0, 1}, " 'rel[str foo, str baz] r = {}; 'x = r\; -"); +", skipCursors = {1}); -test bool relSubscriptWithVar() = {0, 2} == testRenameOccurrences(" +test bool relSubscriptWithVar() = testRenameOccurrences({0, 2}, " 'rel[str foo, str baz] r = {}; 'str foo = \"foo\"; 'x = r\; -"); +", skipCursors = {2}); -test bool tupleFieldSubscriptUpdate() = {0, 1, 2} == testRenameOccurrences(" +test bool tupleFieldSubscriptUpdate() = testRenameOccurrences({0, 1, 2}, " 'tuple[str foo, int baz] t = \<\"one\", 1\>; 'u = t[foo = \"two\"]; 'v = u.foo; "); -test bool tupleFieldAccessUpdate() = {0, 1} == testRenameOccurrences(" +test bool tupleFieldAccessUpdate() = testRenameOccurrences({0, 1}, " 'tuple[str foo, int baz] t = \<\"one\", 1\>; 't.foo = \"two\"; "); -test bool similarCollectionTypes() = {0, 1, 2, 3, 4} == testRenameOccurrences(" +test bool similarCollectionTypes() = testRenameOccurrences({0, 1, 2, 3, 4}, " 'rel[str foo, int baz] r = {}; 'lrel[str foo, int baz] lr = []; 'set[tuple[str foo, int baz]] st = {}; @@ -136,30 +120,18 @@ test bool similarCollectionTypes() = {0, 1, 2, 3, 4} == testRenameOccurrences(" 'tuple[str foo, int baz] t = \<\"\", 0\>; "); -test bool differentRelWithSameFieldAtDef() = {0, 1} == testRenameOccurrences(" +test bool differentRelWithSameField() = testRenameOccurrences({0, 1}, " 'rel[str foo, int baz] r1 = {}; 'foos1 = r1.foo; 'rel[int n, str foo] r2 = {}; 'foos2 = r2.foo; "); -test bool differentRelWithSameFieldAtUse() = {0, 1} == testRenameOccurrences(" - 'rel[str foo, int baz] r1 = {}; - 'foos1 = r1.foo; - 'rel[int n, str foo] r2 = {}; - 'foos2 = r2.foo; -", cursorAtOldNameOccurrence = 1); - -test bool tupleFieldAtDef() = {0, 1} == testRenameOccurrences(" +test bool tupleField() = testRenameOccurrences({0, 1}, " 'tuple[int foo] t = \<8\>; 'y = t.foo; "); -test bool tupleFieldAtUse() = {0, 1} == testRenameOccurrences(" - 'tuple[int foo] t = \<8\>; - 'y = t.foo; -", cursorAtOldNameOccurrence = 1); - // We would prefer an illegalRename exception here @expected{unsupportedRename} test bool builtinFieldSimpleType() = testRename(" diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc index 2fb78bd0a..f6daf01d7 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/FormalParameters.rsc @@ -29,7 +29,7 @@ module lang::rascal::tests::rename::FormalParameters import lang::rascal::tests::rename::TestUtils; import lang::rascal::lsp::refactor::Exception; -test bool outerNestedFunctionParameter() = {0, 3} == testRenameOccurrences(" +test bool outerNestedFunctionParameter() = testRenameOccurrences({0, 3}, " 'int f(int foo) { ' int g(int foo) { ' return foo; @@ -38,48 +38,43 @@ test bool outerNestedFunctionParameter() = {0, 3} == testRenameOccurrences(" '} "); -test bool innerNestedFunctionParameter() = {1, 2} == testRenameOccurrences(" +test bool innerNestedFunctionParameter() = testRenameOccurrences({1, 2}, " 'int f(int foo) { ' int g(int foo) { ' return foo; ' } ' return f(foo); '} -", cursorAtOldNameOccurrence = 1); +"); -test bool publicFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " +test bool publicFunctionParameter() = testRenameOccurrences({0, 1}, "", decls = " 'public int f(int foo) { ' return foo; '} "); -test bool defaultFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " +test bool defaultFunctionParameter() = testRenameOccurrences({0, 1}, "", decls = " 'int f(int foo) { ' return foo; '} "); -test bool privateFunctionParameter() = {0, 1} == testRenameOccurrences("", decls = " +test bool privateFunctionParameter() = testRenameOccurrences({0, 1}, "", decls = " 'private int f(int foo) { ' return foo; '} "); -test bool nestedKeywordParameter() = {0, 1, 2} == testRenameOccurrences(" +test bool nestedKeywordParameter() = testRenameOccurrences({0, 1, 2}, " 'int f(int foo = 8) = foo; 'int x = f(foo = 10); -"); +", skipCursors = {2}); -test bool keywordParameterFromDef() = expectEq({0, 1, 2}, testRenameOccurrences( +test bool keywordParameter() = testRenameOccurrences({0, 1, 2}, "int x = f(foo = 10);" , decls="int f(int foo = 8) = foo;" -)); - -test bool keywordParameterFromUse() = expectEq({0, 1, 2}, testRenameOccurrences( - "int x = f(foo = 10);" - , decls="int f(int foo = 8) = foo;" - , cursorAtOldNameOccurrence = 1 -)); + , skipCursors = {2} +); @expected{illegalRename} test bool doubleParameterDeclaration1() = testRename("int f(int foo, int bar) = 1;"); @expected{illegalRename} test bool doubleParameterDeclaration2() = testRename("int f(int bar, int foo) = 1;"); @@ -90,7 +85,7 @@ test bool keywordParameterFromUse() = expectEq({0, 1, 2}, testRenameOccurrences( @expected{illegalRename} test bool doubleKeywordParameterDeclaration1() = testRename("int f(int foo = 8, int bar = 9) = 1;"); @expected{illegalRename} test bool doubleKeywordParameterDeclaration2() = testRename("int f(int bar = 9, int foo = 8) = 1;"); -test bool renameParamToConstructorName() = {0, 1} == testRenameOccurrences( +test bool renameParamToConstructorName() = testRenameOccurrences({0, 1}, "int f(int foo) = foo;", decls = "data Bar = bar();" ); @@ -101,7 +96,7 @@ test bool renameParamToUsedConstructorName() = testRename( decls = "data Bar = bar(int x);" ); -test bool paremeterShadowsParameter1() = {0, 3} == testRenameOccurrences(" +test bool paremeterShadowsParameter1() = testRenameOccurrences({0, 3}, " 'int f1(int foo) { ' int f2(int foo) { ' int baz = 9; @@ -111,7 +106,7 @@ test bool paremeterShadowsParameter1() = {0, 3} == testRenameOccurrences(" '} "); -test bool paremeterShadowsParameter2() = {1, 2} == testRenameOccurrences(" +test bool paremeterShadowsParameter2() = testRenameOccurrences({1, 2}, " 'int f1(int foo) { ' int f2(int foo) { ' int baz = 9; @@ -119,7 +114,7 @@ test bool paremeterShadowsParameter2() = {1, 2} == testRenameOccurrences(" ' } ' return f2(foo); '} -", cursorAtOldNameOccurrence = 1); +"); @expected{illegalRename} test bool paremeterShadowsParameter3() = testRename(" diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index ffe3266d7..605401d72 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -28,13 +28,13 @@ module lang::rascal::tests::rename::Functions import lang::rascal::tests::rename::TestUtils; -test bool nestedFunctionParameter() = {0, 1} == testRenameOccurrences(" +test bool nestedFunctionParameter() = testRenameOccurrences({0, 1}, " 'int f(int foo, int baz) { ' return foo; '} "); -test bool nestedRecursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences(" +test bool nestedRecursiveFunctionName() = testRenameOccurrences({0, 1, 2, 3}, " 'int fib(int n) { ' switch (n) { ' case 0: { @@ -50,9 +50,9 @@ test bool nestedRecursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences( '} ' 'fib(7); -", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); +", oldName = "fib", newName = "fibonacci"); -test bool recursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences("fib(7);", decls = " +test bool recursiveFunctionName() = testRenameOccurrences({0, 1, 2, 3}, "fib(7);", decls = " 'int fib(int n) { ' switch (n) { ' case 0: { @@ -66,76 +66,58 @@ test bool recursiveFunctionName() = {0, 1, 2, 3} == testRenameOccurrences("fib(7 ' } ' } '} -", oldName = "fib", newName = "fibonacci", cursorAtOldNameOccurrence = -1); +", oldName = "fib", newName = "fibonacci"); -test bool nestedPublicFunction() = {0, 1} == testRenameOccurrences(" +test bool nestedPublicFunction() = testRenameOccurrences({0, 1}, " 'public int foo(int f) { ' return f; '} 'foo(1); "); -test bool nestedDefaultFunction() = {0, 1} == testRenameOccurrences(" +test bool nestedDefaultFunction() = testRenameOccurrences({0, 1}, " 'int foo(int f) { ' return f; '} 'foo(1); "); -test bool nestedPrivateFunction() = {0, 1} == testRenameOccurrences(" +test bool nestedPrivateFunction() = testRenameOccurrences({0, 1}, " 'private int foo(int f) { ' return f; '} 'foo(1); "); -test bool publicFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " +test bool publicFunction() = testRenameOccurrences({0, 1}, "foo(1);", decls = " 'public int foo(int f) { ' return f; '} "); -test bool defaultFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " +test bool defaultFunction() = testRenameOccurrences({0, 1}, "foo(1);", decls = " 'int foo(int f) { ' return f; '} "); -test bool privateFunction() = {0, 1} == testRenameOccurrences("foo(1);", decls = " +test bool privateFunction() = testRenameOccurrences({0, 1}, "foo(1);", decls = " 'private int foo(int f) { ' return f; '} "); -test bool backtrackOverloadFromUse() = {0, 1, 2} == testRenameOccurrences("x = foo(3);", decls = " - 'int foo(int x) = x when x \< 2; - 'default int foo(int x) = x; -", cursorAtOldNameOccurrence = -1); - -test bool backtrackOverloadFromDef() = {0, 1, 2} == testRenameOccurrences("x = foo(3);", decls = " +test bool backtrackOverload() = testRenameOccurrences({0, 1, 2}, "x = foo(3);", decls = " 'int foo(int x) = x when x \< 2; 'default int foo(int x) = x; "); -test bool patternOverloadFromUse() = {0, 1, 2, 3} == testRenameOccurrences("x = size([1, 2]);", decls = " - 'int size(list[&T] _: []) = 0; - 'int size(list[&T] _: [_, *l]) = 1 + size(l); -", cursorAtOldNameOccurrence = -1, oldName = "size", newName = "sizeof"); - -test bool patternOverloadFromDef() = {0, 1, 2, 3} == testRenameOccurrences("x = size([1, 2]);", decls = " +test bool patternOverload() = testRenameOccurrences({0, 1, 2, 3}, "x = size([1, 2]);", decls = " 'int size(list[&T] _: []) = 0; 'int size(list[&T] _: [_, *l]) = 1 + size(l); ", oldName = "size", newName = "sizeof"); -test bool typeOverloadFromUse() = {0, 1, 2, 3, 4, 5, 6} == testRenameOccurrences("x = size([1, 2]);", decls = " - 'int size(list[&T] _: []) = 0; - 'int size(list[&T] _: [_, *l]) = 1 + size(l); - ' - 'int size(set[&T] _: {}) = 0; - 'int size(set[&T] _: {_, *s}) = 1 + size(s); -", cursorAtOldNameOccurrence = -1, oldName = "size", newName = "sizeof"); - -test bool typeOverloadFromDef() = {0, 1, 2, 3, 4, 5, 6} == testRenameOccurrences("x = size([1, 2]);", decls = " +test bool typeOverload() = testRenameOccurrences({0, 1, 2, 3, 4, 5, 6}, "x = size([1, 2]);", decls = " 'int size(list[&T] _: []) = 0; 'int size(list[&T] _: [_, *l]) = 1 + size(l); ' @@ -143,19 +125,13 @@ test bool typeOverloadFromDef() = {0, 1, 2, 3, 4, 5, 6} == testRenameOccurrences 'int size(set[&T] _: {_, *s}) = 1 + size(s); ", oldName = "size", newName = "sizeof"); -test bool arityOverloadFromUse() = {0, 1, 2, 3, 4, 5} == testRenameOccurrences("x = concat(\"foo\", \"bar\");", decls = " - 'str concat(str s) = s; - 'str concat(str s1, str s2) = s1 + concat(s2); - 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); -", cursorAtOldNameOccurrence = -1, oldName = "concat", newName = "foo"); - -test bool arityOverloadFromDef() = {0, 1, 2, 3, 4, 5} == testRenameOccurrences("x = concat(\"foo\", \"bar\");", decls = " +test bool arityOverload() = testRenameOccurrences({0, 1, 2, 3, 4, 5}, "x = concat(\"foo\", \"bar\");", decls = " 'str concat(str s) = s; 'str concat(str s1, str s2) = s1 + concat(s2); 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); ", oldName = "concat", newName = "foo"); -test bool overloadClash() = {0} == testRenameOccurrences("", decls = " +test bool overloadClash() = testRenameOccurrences({0}, "", decls = " 'int foo = 0; 'int foo(int x) = x when x \< 2; "); @@ -171,34 +147,28 @@ test bool crossModuleOverload() = testRenameOccurrences({ ", {0, 1}) }, <"Main", "concat", 0>, newName = "conc"); -test bool simpleTypeParams() = {0, 1} == testRenameOccurrences(" +test bool simpleTypeParams() = testRenameOccurrences({0, 1}, " '&T foo(&T l) = l; + '&T bar(&T x, int y) = x; ", oldName = "T", newName = "U"); -test bool typeParamsFromReturn() = {0, 1, 2} == testRenameOccurrences(" +test bool typeParams() = testRenameOccurrences({0, 1, 2}, " '&T foo(&T l) { ' &T m = l; ' return m; '} ", oldName = "T", newName = "U"); -test bool keywordTypeParamFromReturn() = {0, 1, 2} == testRenameOccurrences(" +test bool keywordTypeParamFromReturn() = testRenameOccurrences({0, 1, 2}, " '&T foo(&T \<: int l, &T \<: int kw = 1) = l + kw; ", oldName = "T", newName = "U"); -test bool typeParamsFromFormal() = {0, 1, 2} == testRenameOccurrences(" - '&T foo(&T l) { - ' &T m = l; - ' return m; - '} -", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); - @expected{illegalRename} test bool typeParamsClash() = testRename(" '&T foo(&T l, &U m) = l; ", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); -test bool nestedTypeParams() = {0, 1, 2, 3} == testRenameOccurrences(" +test bool nestedTypeParams() = testRenameOccurrences({0, 1, 2, 3}, " '&T f(&T t) { ' &T g(&T t) = t; ' return g(t); @@ -213,19 +183,19 @@ test bool nestedTypeParamClash() = testRename(" } ", oldName = "S", newName = "T", cursorAtOldNameOccurrence = 1); -test bool adjacentTypeParams() = {0, 1} == testRenameOccurrences(" +test bool adjacentTypeParams() = testRenameOccurrences({0, 1}, " '&S f(&S s) = s; '&T f(&T t) = t; ", oldName = "S", newName = "T"); -test bool typeParamsListReturn() = {0, 1, 2} == testRenameOccurrences(" +test bool typeParamsListReturn() = testRenameOccurrences({0, 1, 2}, " 'list[&T] foo(&T l) { ' list[&T] m = [l, l]; ' return m; '} -", oldName = "T", newName = "U", cursorAtOldNameOccurrence = 1); +", oldName = "T", newName = "U"); -test bool localOverloadedFunction() = {0, 1, 2, 3} == testRenameOccurrences(" +test bool localOverloadedFunction() = testRenameOccurrences({0, 1, 2, 3}, " 'bool foo(g()) = true; 'bool foo(h()) = foo(g()); ' diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc index 43eff0aef..832234c39 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Performance.rsc @@ -28,14 +28,16 @@ module lang::rascal::tests::rename::Performance import lang::rascal::tests::rename::TestUtils; +import List; + int LARGE_TEST_SIZE = 200; -test bool largeTest() = ({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LARGE_TEST_SIZE], foos := 5 * i) == testRenameOccurrences(( +test bool largeTest() = testRenameOccurrences(({0} | it + {foos + 3, foos + 4, foos + 5} | i <- [0..LARGE_TEST_SIZE], foos := 5 * i), ( "int foo = 8;" | " 'int f(int foo) = foo; 'foo = foo + foo;" | i <- [0..LARGE_TEST_SIZE]) -); +, skipCursors = toSet([1..LARGE_TEST_SIZE * 5])); @expected{unsupportedRename} test bool failOnError() = testRename("int foo = x + y;"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 034a15b0f..2377ad738 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -133,9 +133,21 @@ bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id return true; } -set[int] testRenameOccurrences(str stmtsStr, int cursorAtOldNameOccurrence = 0, str oldName = "foo", str newName = "bar", str decls = "", str imports = "") { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); - return occs; +bool testRenameOccurrences(set[int] oldNameOccurrences, str stmtsStr, str oldName = "foo", str newName = "bar", str decls = "", str imports = "", set[int] skipCursors = {}) { + bool success = true; + map[int, set[int]] results = (); + for (cursor <- oldNameOccurrences - skipCursors) { + <_, renamedOccs, _> = getEditsAndModule(stmtsStr, cursor, oldName, newName, decls, imports); + results[cursor] = renamedOccs; + if (renamedOccs != oldNameOccurrences) success = false; + } + + if (!success) { + println("Test returned unexpected renames for some possible cursors (expected: ):"); + iprintln(results); + } + + return success; } // Test renames that are expected to throw an exception diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index 0e48ea8a7..7ec265ed8 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -28,18 +28,12 @@ module lang::rascal::tests::rename::Types import lang::rascal::tests::rename::TestUtils; -test bool globalAliasFromDef() = {0, 1, 2, 3} == testRenameOccurrences(" +test bool globalAlias() = testRenameOccurrences({0, 1, 2, 3}, " 'Foo f(Foo x) = x; 'Foo foo = 8; 'y = f(foo); ", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar"); -test bool globalAliasFromUse() = {0, 1, 2, 3} == testRenameOccurrences(" - 'Foo f(Foo x) = x; - 'Foo foo = 8; - 'y = f(foo); -", decls = "alias Foo = int;", oldName = "Foo", newName = "Bar", cursorAtOldNameOccurrence = 1); - test bool multiModuleAlias() = testRenameOccurrences({ byText("alg::Fib", "alias Foo = int; 'Foo fib(int n) { @@ -58,7 +52,7 @@ test bool multiModuleAlias() = testRenameOccurrences({ , {0}) }, <"Main", "Foo", 0>, newName = "Bar"); -test bool globalData() = {0, 1, 2, 3} == testRenameOccurrences(" +test bool globalData() = testRenameOccurrences({0, 1, 2, 3}, " 'Foo f(Foo x) = x; 'Foo x = foo(); 'y = f(x); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc index 137031639..9c13b7712 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -32,19 +32,19 @@ import lang::rascal::lsp::refactor::Exception; //// Local -test bool freshName() = {0} == testRenameOccurrences(" +test bool freshName() = testRenameOccurrences({0}, " 'int foo = 8; 'int qux = 10; "); -test bool shadowVariableInInnerScope() = {0} == testRenameOccurrences(" +test bool shadowVariableInInnerScope() = testRenameOccurrences({0}, " 'int foo = 8; '{ ' int bar = 9; '} "); -test bool parameterShadowsVariable() = {0} == testRenameOccurrences(" +test bool parameterShadowsVariable() = testRenameOccurrences({0}, " 'int foo = 8; 'int f(int bar) { ' return bar; @@ -71,7 +71,7 @@ test bool doubleVariableDeclaration() = testRename(" 'int bar = 9; "); -test bool adjacentScopes() = {0} == testRenameOccurrences(" +test bool adjacentScopes() = testRenameOccurrences({0}, " '{ ' int foo = 8; '} @@ -100,14 +100,14 @@ test bool implicitPatterVariableInInnerScopeBecomesUse() = testRename(" '} "); -test bool explicitPatternVariableInInnerScope() = {0} == testRenameOccurrences(" +test bool explicitPatternVariableInInnerScope() = testRenameOccurrences({0}, " 'int foo = 8; 'if (int bar := 9) { ' bar = 2 * bar; '} "); -test bool becomesPatternInInnerScope() = {0} == testRenameOccurrences(" +test bool becomesPatternInInnerScope() = testRenameOccurrences({0}, " 'int foo = 8; 'if (bar : int _ := 9) { ' bar = 2 * bar; @@ -162,9 +162,9 @@ test bool doubleFunctionAndNestedVariableDeclaration() = testRename(" '} "); -test bool tupleVariable() = {0} == testRenameOccurrences("\ = \<0, 1\>;"); +test bool tupleVariable() = testRenameOccurrences({0}, "\ = \<0, 1\>;"); -test bool tuplePatternVariable() = {0, 1} == testRenameOccurrences(" +test bool tuplePatternVariable() = testRenameOccurrences({0, 1}, " 'if (\ := \<0, 1\>) ' qux = foo; "); @@ -172,7 +172,7 @@ test bool tuplePatternVariable() = {0, 1} == testRenameOccurrences(" //// Global -test bool globalVar() = {0, 3} == testRenameOccurrences(" +test bool globalVar() = testRenameOccurrences({0, 3}, " 'int f(int foo) = foo; 'foo = 16; ", decls = " From c900648af454b6b5762f6fa25f6d39090f0bf8d6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 18:21:30 +0200 Subject: [PATCH 079/105] Support type parameter in nested function as cursor. --- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index ab83ae965..9e16183fe 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -263,7 +263,9 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName) DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { AType at = ws.facts[cursorLoc]; - if(Define d: <_, _, _, _, _, defType(afunc(_, /at, _))> <- ws.defines, isContainedIn(cursorLoc, d.defined)) { + set[loc] defs = {}; + set[loc] useDefs = {}; + for (Define d: <_, _, _, _, _, defType(afunc(_, /at, _))> <- ws.defines, isContainedIn(cursorLoc, d.defined)) { // From here on, we can assume that all locations are in the same file, because we are dealing with type parameters and filtered on `isContainedIn` facts = { | l <- ws.facts , at := ws.facts[l] @@ -276,19 +278,19 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLo nextOffsets = toMapUnique(zip2(prefix(offsets), tail(offsets))); loc sentinel = |unknown:///|(0, 0, <0, 0>, <0, 0>); - defs = {min([f | f <- facts<0>, f.offset == nextOffset]) | formal <- formals, nextOffsets[formal.offset]?, nextOffset := nextOffsets[formal.offset]}; + defs += {min([f | f <- facts<0>, f.offset == nextOffset]) | formal <- formals, nextOffsets[formal.offset]?, nextOffset := nextOffsets[formal.offset]}; - useDefs = {trim(l, removePrefix = l.length - size(cursorName)) + useDefs += {trim(l, removePrefix = l.length - size(cursorName)) | l <- facts<0> , !ws.definitions[l]? // If there is a definition at this location, this is a formal argument name , !any(ud <- ws.useDef[l], ws.definitions[ud]?) // If there is a definition for the use at this location, this is a use of a formal argument , !any(flInner <- facts<0>, isStrictlyContainedIn(flInner, l)) // Filter out any facts that contain other facts }; - return ; + } - throw unsupportedRename("Cannot find function definition which defines template variable \'\'"); + return ; } DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { From d2e6287a2bdb7cc85293182a7a915a3b09e3451d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 20 Aug 2024 14:26:48 +0200 Subject: [PATCH 080/105] Run multi-module tests with all expected occurrences as cursor. --- .../lang/rascal/tests/rename/Fields.rsc | 17 +-- .../lang/rascal/tests/rename/Functions.rsc | 23 ++-- .../lang/rascal/tests/rename/Modules.rsc | 38 +----- .../lang/rascal/tests/rename/TestUtils.rsc | 116 ++++++++++-------- .../rascal/lang/rascal/tests/rename/Types.rsc | 29 ++--- .../lang/rascal/tests/rename/Variables.rsc | 2 +- 6 files changed, 91 insertions(+), 134 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index a598aafdd..eba2da575 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -47,7 +47,7 @@ test bool duplicateDataField() = testRename("", decls = "data D = d(int foo, int bar);" ); -test bool crossModuleDataFieldAtDef() = testRenameOccurrences({ +test bool crossModuleDataField() = testRenameOccurrences({ byText("Foo", "data D = a(int foo) | b(int bar);", {0}), byText("Main", " 'import Foo; @@ -56,18 +56,7 @@ test bool crossModuleDataFieldAtDef() = testRenameOccurrences({ ' g = a.foo; '} ", {0}) -}, <"Foo", "foo", 0>); - -test bool crossModuleDataFieldAtUse() = testRenameOccurrences({ - byText("Foo", "data D = a(int foo) | b(int bar);", {0}), - byText("Main", " - 'import Foo; - 'void main() { - ' f = a(8); - ' g = a.foo; - '} - ", {0}) -}, <"Main", "foo", 0>); +}); // TODO Implement data types properly // test bool extendedDataField() = testRenameOccurrences({ @@ -78,7 +67,7 @@ test bool crossModuleDataFieldAtUse() = testRenameOccurrences({ // 'extend Scratch1; // 'data Foo = g(int foo); // ", {0}) -// }, <"Scratch2", "foo", 0>); +// }); test bool relField() = testRenameOccurrences({0, 1}, " 'rel[str foo, str baz] r = {}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc index 605401d72..591fa5995 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Functions.rsc @@ -145,7 +145,7 @@ test bool crossModuleOverload() = testRenameOccurrences({ 'extend Str; 'str concat(str s1, str s2, str s3) = s1 + concat(s2, s3); ", {0, 1}) -}, <"Main", "concat", 0>, newName = "conc"); +}, oldName = "concat", newName = "conc"); test bool simpleTypeParams() = testRenameOccurrences({0, 1}, " '&T foo(&T l) = l; @@ -202,23 +202,14 @@ test bool localOverloadedFunction() = testRenameOccurrences({0, 1, 2, 3}, " 'foo(h()); ", decls = "data D = g() | h();"); -test bool functionsInVModuleStructureFromLeft() = testRenameOccurrences({ +test bool functionsInVModuleStructure() = testRenameOccurrences({ byText("Left", "str foo(str s) = s when s == \"x\";", {0}), byText("Right", "str foo(str s) = s when s == \"y\";", {0}) , byText("Merger", "import Left; 'import Right; 'void main() { x = foo(\"foo\"); } ", {0}) -}, <"Left", "foo", 0>); - -test bool functionsInVModuleStructureFromMerger() = testRenameOccurrences({ - byText("Left", "str foo(str s) = s when s == \"x\";", {0}), byText("Right", "str foo(str s) = s when s == \"y\";", {0}) - , byText("Merger", - "import Left; - 'import Right; - 'void main() { foo(\"foo\"); } - ", {0}) -}, <"Merger", "foo", 0>); +}); test bool functionsInYModuleStructure() = testRenameOccurrences({ byText("Left", "str foo(str s) = s when s == \"x\";", {0}), byText("Right", "str foo(str s) = s when s == \"x\";", {0}) @@ -230,7 +221,7 @@ test bool functionsInYModuleStructure() = testRenameOccurrences({ "import Merger; 'void main() { foo(\"foo\"); } ", {0}) -}, <"Left", "foo", 0>); +}); test bool functionsInInvertedVModuleStructure() = testRenameOccurrences({ byText("Definer", "str foo(str s) = s when s == \"x\"; @@ -238,7 +229,7 @@ test bool functionsInInvertedVModuleStructure() = testRenameOccurrences({ byText("Left", "import Definer; 'void main() { foo(\"foo\"); }", {0}), byText("Right", "import Definer; 'void main() { foo(\"fu\"); }", {0}) -}, <"Left", "foo", 0>); +}); test bool functionsInDiamondModuleStructure() = testRenameOccurrences({ byText("Definer", "str foo(str s) = s when s == \"x\"; @@ -247,7 +238,7 @@ test bool functionsInDiamondModuleStructure() = testRenameOccurrences({ byText("User", "import Left; 'import Right; 'void main() { foo(\"foo\"); }", {0}) -}, <"Definer", "foo", 0>); +}); test bool functionsInIIModuleStructure() = testRenameOccurrences({ byText("LeftDefiner", "str foo(str s) = s when s == \"x\";", {0}), byText("RightDefiner", "str foo(str s) = s when s == \"y\";", {}), @@ -255,4 +246,4 @@ test bool functionsInIIModuleStructure() = testRenameOccurrences({ byText("LeftUser", "import LeftExtender; 'void main() { foo(\"foo\"); }", {0}), byText("RightUser", "import RightExtender; 'void main() { foo(\"fu\"); }", {}) -}, <"LeftDefiner", "foo", 0>); +}); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc index 17630c566..458902d9e 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc @@ -28,20 +28,7 @@ module lang::rascal::tests::rename::Modules import lang::rascal::tests::rename::TestUtils; -test bool singleModuleFromHeader() = testRenameOccurrences({ - byText("Foo", " - 'data Bool = t() | f(); - 'Bool and(Bool l, Bool r) = r is t ? l : f; - ", {0}, newName = "Bar"), - byText("Main", " - 'import Foo; - 'void main() { - ' Foo::Bool b = and(t(), f()); - '} - ", {0, 1}) -}, <"Foo", "Foo", 0>, newName = "Bar"); - -test bool deepModuleFromHeader() = testRenameOccurrences({ +test bool deepModule() = testRenameOccurrences({ byText("some::path::to::Foo", " 'data Bool = t() | f(); 'Bool and(Bool l, Bool r) = r is t ? l : f; @@ -52,9 +39,9 @@ test bool deepModuleFromHeader() = testRenameOccurrences({ ' some::path::to::Foo::Bool b = and(t(), f()); '} ", {0, 1}) -}, <"some::path::to::Foo", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); -test bool shadowedModuleFromHeader() = testRenameOccurrences({ +test bool shadowedModule() = testRenameOccurrences({ byText("Foo", " 'data Bool = t() | f(); 'Bool and(Bool l, Bool r) = r is t ? l : f; @@ -66,22 +53,9 @@ test bool shadowedModuleFromHeader() = testRenameOccurrences({ ' Foo::Bool b = and(t(), f()); '} ", {0, 1}) -}, <"Foo", "Foo", 0>, newName = "Bar"); - -test bool singleModuleFromImport() = testRenameOccurrences({ - byText("Foo", " - 'data Bool = t() | f(); - 'Bool and(Bool l, Bool r) = r is t ? l : f; - ", {0}, newName = "Bar"), - byText("Main", " - 'import Foo; - 'void main() { - ' Foo::Bool b = and(t(), f()); - '} - ", {0, 1}) -}, <"Main", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); -test bool singleModuleFromReference() = testRenameOccurrences({ +test bool singleModule() = testRenameOccurrences({ byText("util::Foo", " 'data Bool = t() | f(); 'Bool and(Bool l, Bool r) = r is t ? l : f; @@ -92,4 +66,4 @@ test bool singleModuleFromReference() = testRenameOccurrences({ ' util::Foo::Bool b = and(t(), f()); '} ", {0, 1}) -}, <"Main", "Foo", 1>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 2377ad738..bea165683 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -50,8 +50,8 @@ import util::Reflective; //// Fixtures and utility functions -data TestModule = byText(str name, str body, set[int] expectedRenameOccs, str newName = name) - | byLoc(loc file, set[int] expectedRenameOccs, str newName = name); +data TestModule = byText(str name, str body, set[int] nameOccs, str newName = name) + | byLoc(loc file, set[int] nameOccs, str newName = name); private list[DocumentEdit] sortEdits(list[DocumentEdit] edits) = [sortChanges(e) | e <- edits]; @@ -67,8 +67,10 @@ private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathC throwAnyErrors(checkAll(root, ccfg)); } -bool expectEq(&T expected, &T actual) { +bool expectEq(&T expected, &T actual, str epilogue = "") { if (expected != actual) { + if (epilogue != "") println(epilogue); + print("EXPECTED: "); iprintln(expected); println(); @@ -81,63 +83,73 @@ bool expectEq(&T expected, &T actual) { return true; } -bool testRenameOccurrences(set[TestModule] modules, tuple[str moduleName, str id, int occ] cursor, str newName = "bar") { - str testName = "Test"; - loc testDir = |memory://tests/rename/|; - - if(any(m <- modules, m is byLoc)) { - testDir = cover([m.file | m <- modules, m is byLoc]); - } else { - // If none of the modules refers to an existing file, clear the test directory before writing files. - remove(testDir); - } - - pcfg = getTestPathConfig(testDir); - modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.expectedRenameOccs, newName = m.newName))}; - cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == cursor.moduleName][0], cursor.id, cursor.occ); - - edits = rascalRenameSymbol(cursorT, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); - - renamesPerModule = ( - beforeRename: afterRename - | renamed(oldLoc, newLoc) <- edits - , beforeRename := getModuleName(oldLoc, pcfg) - , afterRename := getModuleName(newLoc, pcfg) - ); - - replacesPerModule = ( - name: occs - | changed(file, changes) <- edits - , name := getModuleName(file, pcfg) - , locs := {l | replace(l, _) <- changes} - , occs := locsToOccs(parseModuleWithSpaces(file), cursor.id, locs) - ); - - editsPerModule = ( - name : - | srcDir <- pcfg.srcs - , file <- find(srcDir, "rsc") - , name := getModuleName(file, pcfg) - , occs := replacesPerModule[name] ? {} - , nameAfterRename := renamesPerModule[name] ? name - ); +bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str newName = "bar") { + bool success = true; + for (mm <- modules, cursorOcc <- mm.nameOccs) { + str testName = "Test"; + loc testDir = |memory://tests/rename/|; + + if(any(m <- modules, m is byLoc)) { + testDir = cover([m.file | m <- modules, m is byLoc]); + } else { + // If none of the modules refers to an existing file, clear the test directory before writing files. + remove(testDir); + } - expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); + pcfg = getTestPathConfig(testDir); + modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.nameOccs, newName = m.newName))}; + cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == mm.name][0], oldName, cursorOcc); + + println("Renaming \'\' from "); + edits = rascalRenameSymbol(cursorT, toSet(pcfg.srcs), newName, PathConfig(loc _) { return pcfg; }); + + renamesPerModule = ( + beforeRename: afterRename + | renamed(oldLoc, newLoc) <- edits + , beforeRename := getModuleName(oldLoc, pcfg) + , afterRename := getModuleName(newLoc, pcfg) + ); + + replacesPerModule = ( + name: occs + | changed(file, changes) <- edits + , name := getModuleName(file, pcfg) + , locs := {l | replace(l, _) <- changes} + , occs := locsToOccs(parseModuleWithSpaces(file), oldName, locs) + ); + + editsPerModule = ( + name : + | srcDir <- pcfg.srcs + , file <- find(srcDir, "rsc") + , name := getModuleName(file, pcfg) + , occs := replacesPerModule[name] ? {} + , nameAfterRename := renamesPerModule[name] ? name + ); + + expectedEditsPerModule = (name: | m <- modulesByLocation, name := getModuleName(m.file, pcfg)); + + if (!expectEq(expectedEditsPerModule, editsPerModule, epilogue = "Rename from cursor failed:")) { + success = false; + println("Unexpected edits: "); + iprintln(edits); + } - if (!expectEq(expectedEditsPerModule, editsPerModule)) return false; + for (src <- pcfg.srcs) { + verifyTypeCorrectRenaming(src, edits, pcfg); + } - for (src <- pcfg.srcs) { - verifyTypeCorrectRenaming(src, edits, pcfg); + remove(testDir); } - return true; + return success; } bool testRenameOccurrences(set[int] oldNameOccurrences, str stmtsStr, str oldName = "foo", str newName = "bar", str decls = "", str imports = "", set[int] skipCursors = {}) { bool success = true; map[int, set[int]] results = (); for (cursor <- oldNameOccurrences - skipCursors) { - <_, renamedOccs, _> = getEditsAndModule(stmtsStr, cursor, oldName, newName, decls, imports); + <_, renamedOccs> = getEditsAndModule(stmtsStr, cursor, oldName, newName, decls, imports); results[cursor] = renamedOccs; if (renamedOccs != oldNameOccurrences) success = false; } @@ -233,11 +245,11 @@ tuple[list[DocumentEdit], set[int]] getEditsAndOccurrences(loc singleModule, loc } list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports) { - = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); + = getEditsAndModule(stmtsStr, cursorAtOldNameOccurrence, oldName, newName, decls, imports); return edits; } -private tuple[list[DocumentEdit], set[int], loc] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule") { +private tuple[list[DocumentEdit], set[int]] getEditsAndModule(str stmtsStr, int cursorAtOldNameOccurrence, str oldName, str newName, str decls, str imports, str moduleName = "TestModule") { str moduleStr = "module ' @@ -252,7 +264,7 @@ private tuple[list[DocumentEdit], set[int], loc] getEditsAndModule(str stmtsStr, writeFile(moduleFileName, moduleStr); = getEditsAndOccurrences(moduleFileName, testDir, cursorAtOldNameOccurrence, oldName, newName); - return ; + return ; } private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] edits, str name) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index 7ec265ed8..e47c46f1c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -50,7 +50,7 @@ test bool multiModuleAlias() = testRenameOccurrences({ ' return 0; '}" , {0}) - }, <"Main", "Foo", 0>, newName = "Bar"); + }, oldName = "Foo", newName = "Bar"); test bool globalData() = testRenameOccurrences({0, 1, 2, 3}, " 'Foo f(Foo x) = x; @@ -71,25 +71,16 @@ test bool multiModuleData() = testRenameOccurrences({ ' Bool b = and(t(), f()); '}" , {1}) - }, <"Main", "Bool", 1>, newName = "Boolean"); + }, oldName = "Bool", newName = "Boolean"); -test bool dataTypesInVModuleStructureFromLeft() = testRenameOccurrences({ +test bool dataTypesInVModuleStructure() = testRenameOccurrences({ byText("Left", "data Foo = f();", {0}), byText("Right", "data Foo = g();", {0}) , byText("Merger", "import Left; 'import Right; 'bool f(Foo foo) = (foo == f() || foo == g()); ", {0}) -}, <"Left", "Foo", 0>, newName = "Bar"); - -test bool dataTypesInVModuleStructureFromMerger() = testRenameOccurrences({ - byText("Left", "data Foo = f();", {0}), byText("Right", "data Foo = g();", {0}) - , byText("Merger", - "import Left; - 'import Right; - 'bool f(Foo foo) = (foo == f() || foo == g()); - ", {0}) -}, <"Merger", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); @synopsis{ (defs) @@ -110,7 +101,7 @@ test bool dataTypesInWModuleStructureWithoutMerge() = testRenameOccurrences({ "import B; 'import C; 'bool func(Foo foo) = foo == h();", {}) -}, <"D", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); @synopsis{ (defs) @@ -133,7 +124,7 @@ test bool dataTypesInWModuleStructureWithMerge() = testRenameOccurrences({ "import B; 'import C; 'bool func(Foo foo) = foo == h();", {0}) -}, <"D", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); test bool dataTypesInYModuleStructure() = testRenameOccurrences({ byText("Left", "data Foo = f();", {0}), byText("Right", "data Foo = g();", {0}) @@ -145,14 +136,14 @@ test bool dataTypesInYModuleStructure() = testRenameOccurrences({ "import Merger; 'bool f(Foo foo) = (foo == f() || foo == g()); ", {0}) -}, <"Left", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); test bool dataTypesInInvertedVModuleStructure() = testRenameOccurrences({ byText("Definer", "data Foo = f() | g();", {0}), byText("Left", "import Definer; 'bool isF(Foo foo) = foo == f();", {0}), byText("Right", "import Definer; 'bool isG(Foo foo) = foo == g();", {0}) -}, <"Left", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); test bool dataTypesInDiamondModuleStructure() = testRenameOccurrences({ byText("Definer", "data Foo = f() | g();", {0}), @@ -161,7 +152,7 @@ test bool dataTypesInDiamondModuleStructure() = testRenameOccurrences({ 'import Right; 'bool isF(Foo foo) = foo == f(); 'bool isG(Foo foo) = foo == g();", {0, 1}) -}, <"Definer", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); @synopsis{ Two disjunct module trees. Both trees define `data Foo`. Since the trees are disjunct, @@ -173,4 +164,4 @@ test bool dataTypesInIIModuleStructure() = testRenameOccurrences({ byText("LeftUser", "import LeftExtender; 'bool func(Foo foo) = foo == f();", {0}), byText("RightUser", "import RightExtender; 'bool func(Foo foo) = foo == g();", {}) -}, <"LeftDefiner", "Foo", 0>, newName = "Bar"); +}, oldName = "Foo", newName = "Bar"); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc index 9c13b7712..ae87d9dd5 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -196,4 +196,4 @@ test bool multiModuleVar() = testRenameOccurrences({ ' return 0; '}" , {0}) - }, <"Main", "foo", 0>, newName = "Bar"); + }); From 83e3b7b5e361e21f4be0c0a7ec1a04f7b038f7ef Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 20 Aug 2024 15:07:38 +0200 Subject: [PATCH 081/105] Mark cursor as module name less aggressively. --- .../rascal/lang/rascal/lsp/refactor/Rename.rsc | 7 ++++++- .../rascal/lang/rascal/tests/rename/Modules.rsc | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 266e2f8af..26dd028bf 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -305,7 +305,12 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (d <- defs, just(amodule(_)) := getFact(ws, d)) { // Cursor is at an import cur = cursor(moduleName(), c, cursorName); - } else if (u <- ws.useDef<0>, u.begin <= cursorLoc.begin && u.end > cursorLoc.end) { + } else if (u <- ws.useDef<0> + , isContainedIn(cursorLoc, u) + , u.end > cursorLoc.end + // If the cursor is on a variable, we expect a module variable (`moduleVariable()`); not a local (`variableId()`) + , {variableId()} !:= (ws.defines)[getDefs(ws, u)] + ) { // Cursor is at a qualified name cur = cursor(moduleName(), c, cursorName); } else if (defines != {}) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc index 458902d9e..9b0299864 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Modules.rsc @@ -41,7 +41,7 @@ test bool deepModule() = testRenameOccurrences({ ", {0, 1}) }, oldName = "Foo", newName = "Bar"); -test bool shadowedModule() = testRenameOccurrences({ +test bool shadowedModuleWithVar() = testRenameOccurrences({ byText("Foo", " 'data Bool = t() | f(); 'Bool and(Bool l, Bool r) = r is t ? l : f; @@ -55,6 +55,19 @@ test bool shadowedModule() = testRenameOccurrences({ ", {0, 1}) }, oldName = "Foo", newName = "Bar"); +test bool shadowedModuleWithFunc() = testRenameOccurrences({ + byText("Foo", " + 'void f() { fail; } + ", {0}, newName = "Bar"), + byText("shadow::Foo", "", {}), + byText("Main", " + 'import Foo; + 'void main() { + ' Foo::f(); + '} + ", {0, 1}) +}, oldName = "Foo", newName = "Bar"); + test bool singleModule() = testRenameOccurrences({ byText("util::Foo", " 'data Bool = t() | f(); From ed62e205dc726e6fa629c4db822f9017e4c2c851 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 16 Aug 2024 14:37:40 +0200 Subject: [PATCH 082/105] Extend/improve data type tests. --- .../lang/rascal/tests/rename/Fields.rsc | 137 +++++++++++++++--- 1 file changed, 117 insertions(+), 20 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index eba2da575..01fe95dde 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -29,25 +29,87 @@ module lang::rascal::tests::rename::Fields import lang::rascal::tests::rename::TestUtils; import lang::rascal::lsp::refactor::Exception; -test bool dataField() = testRenameOccurrences({0, 1}, " +test bool constructorField() = testRenameOccurrences({0, 1}, " 'D oneTwo = d(1, 2); - 'x = d.foo; + 'x = oneTwo.foo; ", decls = "data D = d(int foo, int baz);" ); -// TODO Implement data types properly -// test bool multipleConstructorDataField() = testRenameOccurrences({0, 1, 2}, " -// 'x = d(1, 2); -// 'y = x.foo; -// ", decls = "data D = d(int foo) | d(int foo, int baz);" -// , cursorAtOldNameOccurrence = 1); +test bool constructorKeywordField() = testRenameOccurrences({0, 1, 2}, " + 'D dd = d(foo=1, baz=2); + 'x = dd.foo; + ", decls="data D = d(int foo = 0, int baz = 0);" +); + +test bool commonKeywordField() = testRenameOccurrences({0, 1, 2}, " + 'D oneTwo = d(foo=1, baz=2); + 'x = oneTwo.foo; + ", decls = "data D(int foo = 0, int baz = 0) = d();" +); + +test bool multipleConstructorField() = testRenameOccurrences({0, 1, 2}, " + 'x = d(1, 2); + 'y = x.foo; + ", decls = "data D = d(int foo) | d(int foo, int baz);" +); @expected{illegalRename} -test bool duplicateDataField() = testRename("", decls = +test bool duplicateConstructorField() = testRename("", decls = "data D = d(int foo, int bar);" ); -test bool crossModuleDataField() = testRenameOccurrences({ +@expected{illegalRename} +test bool differentTypeAcrossConstructorField() = testRename("", decls = + "data D = d0(int foo) | d1(str bar);" +); + +test bool sameTypeFields() = testRenameOccurrences({0, 1}, + "x = d({}, {}); + 'xx = x.foo; + 'xy = x.baz; + ", + decls = "data D = d(set[loc] foo, set[loc] baz);" +); + +test bool commonKeywordFieldsSameType() = testRenameOccurrences({0, 1}, + "x = d(); + 'xx = x.foo; + 'xy = x.baz; + ", + decls = "data D (set[loc] foo = {}, set[loc] baz = {})= d();" +); + +test bool complexDataType() = testRenameOccurrences({0, 1}, + "WorkspaceInfo ws = workspaceInfo( + ' ProjectFiles() { return {}; }, + ' ProjectFiles() { return {}; }, + ' set[TModel](ProjectFiles projectFiles) { return { tmodel() }; } + '); + 'ws.projects += {}; + 'ws.sourceFiles += {}; + ", + decls = " + 'data TModel = tmodel(); + 'data Define; + 'data AType; + 'alias ProjectFiles = rel[loc projectFolder, loc file]; + 'data WorkspaceInfo ( + ' // Instance fields + ' // Read-only + ' rel[loc use, loc def] useDef = {}, + ' set[Define] defines = {}, + ' set[loc] sourceFiles = {}, + ' map[loc, Define] definitions = (), + ' map[loc, AType] facts = (), + ' set[loc] projects = {} + ') = workspaceInfo( + ' ProjectFiles() preloadFiles, + ' ProjectFiles() allFiles, + ' set[TModel](ProjectFiles) tmodelsForLocs + ');" +, oldName = "sourceFiles", newName = "sources"); + +test bool crossModuleConstructorField() = testRenameOccurrences({ byText("Foo", "data D = a(int foo) | b(int bar);", {0}), byText("Main", " 'import Foo; @@ -58,16 +120,51 @@ test bool crossModuleDataField() = testRenameOccurrences({ ", {0}) }); -// TODO Implement data types properly -// test bool extendedDataField() = testRenameOccurrences({ -// byText("Scratch1", " -// 'data Foo = f(int foo); -// ", {0}), -// byText("Scratch2", " -// 'extend Scratch1; -// 'data Foo = g(int foo); -// ", {0}) -// }); +test bool extendedConstructorField() = testRenameOccurrences({ + byText("Scratch1", " + 'data Foo = f(int foo); + ", {0}), + byText("Scratch2", " + 'extend Scratch1; + 'data Foo = g(int foo); + ", {0}) +}); + +test bool dataTypeReusedName() = testRenameOccurrences({ + byText("Scratch1", " + 'data Foo = f(); + ", {0}), + byText("Scratch2", " + 'data Foo = g(); + ", {}) +}); + +test bool dataFieldReusedName() = testRenameOccurrences({ + byText("Scratch1", " + 'data Foo = f(int foo); + ", {0}), + byText("Scratch2", " + 'data Foo = g(int foo); + ", {}) +}); + +test bool dataKeywordFieldReusedName() = testRenameOccurrences({ + byText("Scratch1", " + 'data Foo = f(int foo = 0); + ", {0}), + byText("Scratch2", " + 'data Foo = g(int foo = 0); + ", {}) +}); + +test bool dataCommonKeywordFieldReusedName() = testRenameOccurrences({ + byText("Scratch1", " + 'data Foo(int foo = 0) = f(); + ", {0}), + byText("Scratch2", " + 'data Foo(int foo = 0) = g(); + ", {}) +}); test bool relField() = testRenameOccurrences({0, 1}, " 'rel[str foo, str baz] r = {}; From f852237d6109ba167c09f8e2305f1acd5fc53d07 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 19 Aug 2024 11:51:09 +0200 Subject: [PATCH 083/105] Basic implementation of constructor field renaming. --- .../lang/rascal/lsp/refactor/Rename.rsc | 141 ++++++++++++++++-- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 123 +++++++++++++++ .../lang/rascal/tests/rename/TestUtils.rsc | 11 ++ 3 files changed, 262 insertions(+), 13 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 26dd028bf..a7951eba9 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -249,10 +249,26 @@ private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(def(), cursorLoc, _) private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)) = rascalIsFunctionLocalDefs(ws, rascalGetOverloadedDefs(ws, getDefs(ws, cursorLoc), rascalMayOverloadSameName)); private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; -private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(collectionField(), _, _)) = false; -private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(moduleName(), _, _)) = false; private default bool rascalIsFunctionLocal(_, _) = false; +private Define getADTDefinition(WorkspaceInfo ws, AType lhsType, loc lhs) { + rel[loc, Define] definitionsRel = toRel(ws.definitions); + if (rascalIsConstructorType(lhsType) + , Define cons: <_, _, _, constructorId(), _, _> <- definitionsRel[rascalReachableDefs(ws, getDefs(ws, lhs))] + , AType consAdtType := cons.defInfo.atype.adt + , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- definitionsRel[rascalReachableDefs(ws, {cons.defined})] + , isContainedIn(cons.defined, adt.defined)) { // Probably need to follow import paths here as well + return adt; + } else if (rascalIsDataType(lhsType) + , Define ctr:<_, _, _, constructorId(), _, defType(acons(lhsType, _, _))> <- definitionsRel[rascalReachableDefs(ws, getDefs(ws, lhs))] + , Define adt:<_, _, _, dataId(), _, defType(lhsType)> <- definitionsRel[rascalReachableDefs(ws, {ctr.defined})] + , isContainedIn(ctr.defined, adt.defined)) { + return adt; + } + + throw "Unknown LHS type "; +} + tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; str cursorName = ""; @@ -265,8 +281,17 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , loc fieldLoc <- fieldLocs }; + // rel[loc kw, loc container] keywords = { + // | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) + // , just() := rascalGetKeywordLocs(cursorName, t) + // , loc kwLoc <- kwLocs + // }; + + // print("All fields: "); + // iprintln(fields); Maybe[loc] smallestFieldContainingCursor = findSmallestContaining(fields.field, cursorLoc); + // Maybe[loc] smallestKeywordContainingCursor = findSmallestContaining(keywords.kw, cursorLoc); rel[loc l, CursorKind kind] locsContainingCursor = { @@ -279,6 +304,13 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , // Any kind of field; we'll decide which exactly later , + , + // , + // , + // Any kind of keyword param; we'll decide which exactly later + // , + // , + // , // Module name declaration, where the cursor location is in the module header , } @@ -288,12 +320,79 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { throw unsupportedRename("Renaming \'\' at is not supported."); } + // print("Defines: "); + // iprintln(ws.definitions); + + // print("Facts: "); + // iprintln(ws.facts); + + bool rascalAdtHasCommonKeywordField(str fieldName, <_, _, _, dataId(), _, DefInfo defInfo>) { + if (defInfo.commonKeywordFields?) { + for ((KeywordFormal) ` = ` <- defInfo.commonKeywordFields, "" == fieldName) { + return true; + } + } + return false; + } + + bool rascalConsHasKeywordField(str fieldName, <_, _, _, constructorId(), _, defType(acons(_, _, kwFields))>) { + for (kwField(_, fieldName, _) <- kwFields) return true; + return false; + } + + bool rascalConsHasField(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, fields, _))>) { + for (field <- fields) { + if (field.alabel == fieldName) return true; + } + return false; + } + + loc c = min(locsContainingCursor.l); Cursor cur = cursor(use(), |unknown:///|, ""); + print("Locs containing cursor: "); + iprintln(locsContainingCursor); switch (locsContainingCursor[c]) { case {moduleName(), *_}: { cur = cursor(moduleName(), c, cursorName); } + // case {def(), dataKeywordField(_), dataCommonKeywordField(_), keywordParam()}: { + + // } + case {collectionField(), dataField(_), *_}: { //, dataKeywordField(_), dataCommonKeywordField(_), *_}: { + /* Possible cases: + 0. We are on a field use/access (of either a data or collection field, in an expression/assignment/pattern(?)) + 1. We are on a collection field + 2. We are on a positional field definition (inside a constructor variant, inside a data def) + 3. We are on a keyword field definition (inside a constructor variant) + 4. We are on a common keyword field definition (inside a data def) + 5. We are on a (common) keyword argument (inside a constructor call) + */ + + // Let's figure out what kind of field we are exactly + if ({loc container} := fields[c], maybeContainerType := getFact(ws, container)) { + if ((just(containerType) := maybeContainerType && rascalIsCollectionType(containerType)) + || maybeContainerType == nothing()) { + // Case 1 (or 0): collection field + cur = cursor(collectionField(), c, cursorName); + } else if (just(containerType) := maybeContainerType + , Define dt := printlnExp("ADT def: ", getADTDefinition(ws, containerType, container)) + , adtType := dt.defInfo.atype) { + if (rascalAdtHasCommonKeywordField(cursorName, dt)) { + // Case 4 or 5 (or 0): common keyword field + cur = cursor(dataCommonKeywordField(dt.defined), c, cursorName); + } else if (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { + if (rascalConsHasKeywordField(cursorName, d)) { + // Case 3 (or 0): keyword field + cur = cursor(dataKeywordField(dt.defined), c, cursorName); + } else if (rascalConsHasField(cursorName, d)) { + // Case 2 (or 0): positional field + cur = cursor(dataField(dt.defined), c, cursorName); + } + } + } + } + } case {def(), *_}: { // Cursor is at a definition cur = cursor(def(), c, cursorName); @@ -313,6 +412,14 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { ) { // Cursor is at a qualified name cur = cursor(moduleName(), c, cursorName); + // } else if (/Tree t := parseModuleWithSpacesCached(cursorLoc.top) + // , just() := getFieldLoc(cursorName, t) + // , just(acons(adtType, _, _)) := getFact(ws, lhs) + // , Define dataDef: <_, _, _, dataId(), _, defType(AType adtType)> <- ws.defines + // , Define kwDef: <_, cursorName, _, keywordFormalId(), _, _> <- ws.defines + // , isStrictlyContainedIn(kwDef.defined, dataDef.defined)) { + // // Cursor is at a field use + // cur = cursor(dataField(), kwDef.defined, cursorName); } else if (defines != {}) { // The cursor is at a use with corresponding definitions. cur = cursor(use(), c, cursorName); @@ -329,6 +436,8 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (cur.l.scheme == "unknown") throw unsupportedRename("Could not retrieve information for \'\' at ."); + println("Cursor: "); + return ; } @@ -391,16 +500,16 @@ private bool rascalContainsName(loc l, str name) { 3. It does not change definitions outside of the current workspace. } list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) - = job("renaming to ", list[DocumentEdit](void(str, int) step) { + { // }= job("renaming to ", list[DocumentEdit](void(str, int) step) { loc cursorLoc = cursorT.src; str cursorName = ""; - step("collecting workspace information", 1); + // step("collecting workspace information", 1); WorkspaceInfo ws = workspaceInfo( // Preload ProjectFiles() { return { < - min([f | f <- workspaceFolders, isPrefixOf(f, cursorLoc)]), + max([f | f <- workspaceFolders, isPrefixOf(f, cursorLoc)]), cursorLoc.top > }; }, @@ -435,18 +544,24 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s } ); - step("analyzing name at cursor", 1); + // step("analyzing name at cursor", 1); = rascalGetCursor(ws, cursorT); - step("loading required type information", 1); + // step("loading required type information", 1); if (!rascalIsFunctionLocal(ws, cur)) { - println("Renaming not module-local; loading more information from workspace."); + // println("Renaming not module-local; loading more information from workspace."); ws = loadWorkspace(ws); - } else { - println("Renaming guaranteed to be module-local."); + // } else { + // println("Renaming guaranteed to be module-local."); } - step("collecting uses of \'\'", 1); + // print("Defines: "); + // iprintln({ | d <- ws.defines}["Foo"]); + + // print("Scopes: "); + // iprintln(ws.scopes); + + // step("collecting uses of \'\'", 1); = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName, getPathConfig); rel[loc file, loc defines] defsPerFile = { | d <- defs}; @@ -454,7 +569,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s set[loc] \files = defsPerFile.file + usesPerFile.file; - step("checking rename validity", 1); + // step("checking rename validity", 1); map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults = (file: | file <- \files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName)); @@ -467,7 +582,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s list[DocumentEdit] renames = [renamed(from, to) | <- getRenames(newName)]; return changes + renames; -}, totalWork = 5); +}//, totalWork = 5); //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 9e16183fe..80d55b156 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -47,11 +47,17 @@ import Map; import Set; import String; +import IO; + data CursorKind = use() | def() | typeParam() | collectionField() + | dataField(loc dataTypeDef) + | dataKeywordField(loc dataTypeDef) + | dataCommonKeywordField(loc dataTypeDef) + | keywordParam() | moduleName() ; @@ -161,6 +167,20 @@ rel[loc from, loc to] rascalGetTransitiveReflexiveModulePaths(WorkspaceInfo ws) ; } +@memo{maximumSize=1, minutes=5} +rel[loc from, loc to] rascalGetTransitiveReflexiveScopes(WorkspaceInfo ws) = toRel(ws.scopes)*; + +set[loc] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { + rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); + rel[loc from, loc to] scopes = rascalGetTransitiveReflexiveScopes(ws); + rel[loc from, loc to] reachableDefs = + (ws.defines)[defs] + o scopes // Find the module scope + o modulePaths + o ws.defines; + return reachableDefs.to; +} + set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { set[loc] overloadedDefs = defs; @@ -194,6 +214,8 @@ private rel[loc, loc] NO_RENAMES(str _) = {}; private int qualSepSize = size("::"); bool rascalIsCollectionType(AType at) = at is arel || at is alrel || at is atuple; +bool rascalIsConstructorType(AType at) = at is acons; +bool rascalIsDataType(AType at) = at is aadt; set[loc] rascalGetKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = {}; @@ -213,6 +235,12 @@ set[loc] rascalGetKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorN return uses; } +set[loc] rascalGetKeywordArgs(none(), _) = {}; +set[loc] rascalGetKeywordArgs(\default(_, kwArgList), str cursorName) = + { kwArg.name.src + | kwArg <- kwArgList + , "" == cursorName}; + set[loc] rascalGetKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = getUses(ws, defs); @@ -293,6 +321,84 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLo return ; } +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { + set[loc] initialDefs = {}; + if (cursorLoc in ws.useDef<0>) { + // println("Data field is a use"); + initialDefs = getDefs(ws, cursorLoc); + } else if (cursorLoc in ws.defines) { + // println("Data field is a def"); + initialDefs = {cursorLoc}; + } else if (just(AType adtType) := getFact(ws, cursorLoc)) { + // println("Data field is something else"); + initialDefs = { + kwDef.defined + | Define dataDef: <_, _, _, dataId(), _, defType(adtType)> <- ws.defines + , Define kwDef: <_, _, cursorName, keywordFormalId(), _, _> <- ws.defines + , isStrictlyContainedIn(kwDef.defined, dataDef.defined) + }; + } else { + throw unsupportedRename("Cannot rename data field \'\' from "); + } + + set[loc] defs = rascalGetOverloadedDefs(ws, initialDefs, mayOverloadF); + set[loc] uses = getUses(ws, defs) + getKeywordFieldUses(ws, defs, cursorName); + + return ; +} + +bool debug = false; + +&T printlnExpD(str msg, &T t) = debug ? printlnExp(msg, t) : t; + +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataKeywordField(loc adtLoc), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { + set[loc] defs = {}; + set[loc] uses = {}; + + print("Defs: "); + iprintln(ws.defines); + + print("Use/defs: "); + iprintln(ws.useDef); + + set[loc] adtDefs = rascalGetOverloadedDefs(ws, {adtLoc}, mayOverloadF); + for (d <- adtDefs, dataTypeDef := ws.definitions[d], dataType := dataTypeDef.defInfo.atype) { + for (Define c:<_, _, _, constructorId(), _, _> <- ws.defines) { + for (u <- getUses(ws, c.defined) + c.defined) { + if (f <- sort(factsInvert(ws)[dataType], byLength) + , isContainedIn(u, f) + , m := parseModuleWithSpacesCached(f.top) + , /Tree t := m + , t.src?, t.src == f + , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { + print("Keyword locs (of use ): "); + iprintln(kwLocs); + + uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); + } + } + } + for (Define dt:<_, _, _, _, _, defType(acons(dataType, _, _))> <- ws.defines + , dt.idRole != dataId()) { + println("dt:
"); + if (f <- sort(factsInvert(ws)[dataType], desc(byLength)) + , isContainedIn(dt.defined, f)) { + m = parseModuleWithSpacesCached(f.top); + for (/Tree t := m + , t.src?, isContainedIn(t.src, f) + , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { + print("Keyword locs (of ): "); + iprintln(kwLocs); + + uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); + } + } + } + } + + return ; +} + DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { bool isTupleField(AType fieldType) = fieldType.alabel == ""; @@ -340,6 +446,23 @@ Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (StructuredType) ` default Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, Tree _) = nothing(); +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Expression) `(<{Expression ","}* _> )`) { + kwArgLocs = rascalGetKeywordArgs(kwArgs, fieldName); + return kwArgLocs != {} ? just() : nothing(); +} + +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Pattern) `(<{Pattern ","}* _> )`) { + kwArgLocs = rascalGetKeywordArgs(kwArgs, fieldName); + return kwArgLocs != {} ? just() : nothing(); +} + +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Variant) `(<{TypeArg ","}* _> )`) { + kwArgLocs = rascalGetKeywordArgs(kwArgs, fieldName); + return kwArgLocs != {} ? just() : nothing(); +} + +default Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, Tree _) = nothing(); + private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { set[loc] containerFacts = factsInvert(ws)[containerType]; rel[loc file, loc u] factsByModule = groupBy(containerFacts, loc(loc l) { return l.top; }); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index bea165683..85ca15e05 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -29,6 +29,8 @@ module lang::rascal::tests::rename::TestUtils import lang::rascal::lsp::refactor::Rename; // Module under test +import lang::rascal::lsp::refactor::Util; + import IO; import List; import Location; @@ -64,6 +66,10 @@ private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathC executeDocumentEdits(sortEdits(edits)); remove(pcfg.resources); RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true][verbose = false][logPathConfig = false]; + // for (f <- find(root, "rsc")) { + // println(f); + // println(parseModuleWithSpaces(f)); + // } throwAnyErrors(checkAll(root, ccfg)); } @@ -237,6 +243,8 @@ tuple[list[DocumentEdit], set[int]] getEditsAndOccurrences(loc singleModule, loc edits = getEdits(singleModule, {projectDir}, cursorAtOldNameOccurrence, oldName, newName, PathConfig(loc _) { return pcfg; }); occs = extractRenameOccurrences(singleModule, edits, oldName); + // println("Occurrences: "); + for (src <- pcfg.srcs) { verifyTypeCorrectRenaming(src, edits, pcfg); } @@ -274,6 +282,9 @@ private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] oldNameOccurrences += n.src; } + print("All locations of \'\': "); + iprintln(sort(oldNameOccurrences, byOffset)); + if ([changed(_, replaces)] := edits) { set[int] idx = {}; for (replace(replaceAt, replaceWith) <- replaces) { From 2fc4ec7ef8436c640db365a2942f4d5f71144069 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 21 Aug 2024 14:04:18 +0200 Subject: [PATCH 084/105] Implement basic constructor keyword fields. --- .../lang/rascal/lsp/refactor/Rename.rsc | 110 +++++++++--------- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 2 +- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index a7951eba9..5d2a623fc 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -251,12 +251,12 @@ private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _) private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; private default bool rascalIsFunctionLocal(_, _) = false; -private Define getADTDefinition(WorkspaceInfo ws, AType lhsType, loc lhs) { +private Define rascalGetADTDefinition(WorkspaceInfo ws, AType lhsType, loc lhs) { rel[loc, Define] definitionsRel = toRel(ws.definitions); - if (rascalIsConstructorType(lhsType) - , Define cons: <_, _, _, constructorId(), _, _> <- definitionsRel[rascalReachableDefs(ws, getDefs(ws, lhs))] + if (printlnExpD("Is constructor type []: ", rascalIsConstructorType(lhsType)) + , Define cons: <_, _, _, constructorId(), _, _> <- printlnExpD("Reachable defs (from lhs): ", definitionsRel[rascalReachableDefs(ws, printlnExpD("Defs of lhs: ", getDefs(ws, lhs)) + lhs)]) , AType consAdtType := cons.defInfo.atype.adt - , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- definitionsRel[rascalReachableDefs(ws, {cons.defined})] + , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- printlnExpD("Reachable defs (from constructor def): ", definitionsRel[rascalReachableDefs(ws, {cons.defined})]) , isContainedIn(cons.defined, adt.defined)) { // Probably need to follow import paths here as well return adt; } else if (rascalIsDataType(lhsType) @@ -269,6 +269,27 @@ private Define getADTDefinition(WorkspaceInfo ws, AType lhsType, loc lhs) { throw "Unknown LHS type "; } +bool rascalAdtHasCommonKeywordField(str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) { + if (defInfo.commonKeywordFields?) { + for ((KeywordFormal) ` = ` <- defInfo.commonKeywordFields, "" == fieldName) { + return true; + } + } + return false; +} + +bool rascalConsHasKeywordField(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, _, kwFields))>) { + for (kwField(_, fieldName, _) <- kwFields) return true; + return false; +} + +bool rascalConsHasField(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, fields, _))>) { + for (field <- fields) { + if (field.alabel == fieldName) return true; + } + return false; +} + tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; str cursorName = ""; @@ -281,17 +302,14 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , loc fieldLoc <- fieldLocs }; - // rel[loc kw, loc container] keywords = { - // | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) - // , just() := rascalGetKeywordLocs(cursorName, t) - // , loc kwLoc <- kwLocs - // }; - - // print("All fields: "); - // iprintln(fields); + rel[loc kw, loc container] keywords = { + | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) + , just() := rascalGetKeywordLocs(cursorName, t) + , loc kwLoc <- kwLocs + }; Maybe[loc] smallestFieldContainingCursor = findSmallestContaining(fields.field, cursorLoc); - // Maybe[loc] smallestKeywordContainingCursor = findSmallestContaining(keywords.kw, cursorLoc); + Maybe[loc] smallestKeywordContainingCursor = findSmallestContaining(keywords.kw, cursorLoc); rel[loc l, CursorKind kind] locsContainingCursor = { @@ -305,12 +323,12 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { // Any kind of field; we'll decide which exactly later , , - // , + , // , // Any kind of keyword param; we'll decide which exactly later - // , + , // , - // , + , // Module name declaration, where the cursor location is in the module header , } @@ -326,28 +344,27 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { // print("Facts: "); // iprintln(ws.facts); - bool rascalAdtHasCommonKeywordField(str fieldName, <_, _, _, dataId(), _, DefInfo defInfo>) { - if (defInfo.commonKeywordFields?) { - for ((KeywordFormal) ` = ` <- defInfo.commonKeywordFields, "" == fieldName) { - return true; + + Cursor getDataFieldCursor(AType containerType, loc container) { + if (Define dt := rascalGetADTDefinition(ws, containerType, container) + , adtType := dt.defInfo.atype) { + if (rascalAdtHasCommonKeywordField(cursorName, dt)) { + // Case 4 or 5 (or 0): common keyword field + return cursor(dataCommonKeywordField(dt.defined), c, cursorName); + } else if (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { + if (rascalConsHasKeywordField(cursorName, d)) { + // Case 3 (or 0): keyword field + return cursor(dataKeywordField(dt.defined), c, cursorName); + } else if (rascalConsHasField(cursorName, d)) { + // Case 2 (or 0): positional field + return cursor(dataField(dt.defined), c, cursorName); + } } } - return false; - } - - bool rascalConsHasKeywordField(str fieldName, <_, _, _, constructorId(), _, defType(acons(_, _, kwFields))>) { - for (kwField(_, fieldName, _) <- kwFields) return true; - return false; - } - bool rascalConsHasField(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, fields, _))>) { - for (field <- fields) { - if (field.alabel == fieldName) return true; - } - return false; + throw "Cannot derive data field information for at "; } - loc c = min(locsContainingCursor.l); Cursor cur = cursor(use(), |unknown:///|, ""); print("Locs containing cursor: "); @@ -356,10 +373,12 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { case {moduleName(), *_}: { cur = cursor(moduleName(), c, cursorName); } - // case {def(), dataKeywordField(_), dataCommonKeywordField(_), keywordParam()}: { - - // } - case {collectionField(), dataField(_), *_}: { //, dataKeywordField(_), dataCommonKeywordField(_), *_}: { + case {keywordParam(), dataKeywordField(_), *_}: { + if ({loc container} := keywords[c], just(containerType) := getFact(ws, container)) { + cur = getDataFieldCursor(containerType, container); + } + } + case {collectionField(), dataField(_), dataKeywordField(_), *_}: { //, dataCommonKeywordField(_), *_}: { /* Possible cases: 0. We are on a field use/access (of either a data or collection field, in an expression/assignment/pattern(?)) 1. We are on a collection field @@ -375,21 +394,8 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { || maybeContainerType == nothing()) { // Case 1 (or 0): collection field cur = cursor(collectionField(), c, cursorName); - } else if (just(containerType) := maybeContainerType - , Define dt := printlnExp("ADT def: ", getADTDefinition(ws, containerType, container)) - , adtType := dt.defInfo.atype) { - if (rascalAdtHasCommonKeywordField(cursorName, dt)) { - // Case 4 or 5 (or 0): common keyword field - cur = cursor(dataCommonKeywordField(dt.defined), c, cursorName); - } else if (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { - if (rascalConsHasKeywordField(cursorName, d)) { - // Case 3 (or 0): keyword field - cur = cursor(dataKeywordField(dt.defined), c, cursorName); - } else if (rascalConsHasField(cursorName, d)) { - // Case 2 (or 0): positional field - cur = cursor(dataField(dt.defined), c, cursorName); - } - } + } else if (just(containerType) := maybeContainerType) { + cur = getDataFieldCursor(containerType, container); } } } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 80d55b156..d6f375464 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -342,7 +342,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc) } set[loc] defs = rascalGetOverloadedDefs(ws, initialDefs, mayOverloadF); - set[loc] uses = getUses(ws, defs) + getKeywordFieldUses(ws, defs, cursorName); + set[loc] uses = getUses(ws, defs) + rascalGetKeywordFieldUses(ws, defs, cursorName); return ; } From 9ffbaff121730a8c8e0199a64b11a78cbee4ba41 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 22 Aug 2024 18:12:45 +0200 Subject: [PATCH 085/105] Implement common keyword fields. --- .../lang/rascal/lsp/refactor/Rename.rsc | 40 +++--- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 114 +++++++++--------- 2 files changed, 82 insertions(+), 72 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 5d2a623fc..257a24c84 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -251,22 +251,26 @@ private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _) private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; private default bool rascalIsFunctionLocal(_, _) = false; -private Define rascalGetADTDefinition(WorkspaceInfo ws, AType lhsType, loc lhs) { +private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, AType lhsType, loc lhs) { rel[loc, Define] definitionsRel = toRel(ws.definitions); - if (printlnExpD("Is constructor type []: ", rascalIsConstructorType(lhsType)) - , Define cons: <_, _, _, constructorId(), _, _> <- printlnExpD("Reachable defs (from lhs): ", definitionsRel[rascalReachableDefs(ws, printlnExpD("Defs of lhs: ", getDefs(ws, lhs)) + lhs)]) - , AType consAdtType := cons.defInfo.atype.adt - , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- printlnExpD("Reachable defs (from constructor def): ", definitionsRel[rascalReachableDefs(ws, {cons.defined})]) - , isContainedIn(cons.defined, adt.defined)) { // Probably need to follow import paths here as well - return adt; - } else if (rascalIsDataType(lhsType) - , Define ctr:<_, _, _, constructorId(), _, defType(acons(lhsType, _, _))> <- definitionsRel[rascalReachableDefs(ws, getDefs(ws, lhs))] - , Define adt:<_, _, _, dataId(), _, defType(lhsType)> <- definitionsRel[rascalReachableDefs(ws, {ctr.defined})] - , isContainedIn(ctr.defined, adt.defined)) { - return adt; + + set[Define] defs = {}; + if (printlnExpD("Is constructor type []: ", rascalIsConstructorType(lhsType))) { + for (Define cons: <_, _, _, constructorId(), _, _> <- printlnExpD("Reachable defs (from lhs): ", definitionsRel[rascalReachableDefs(ws, printlnExpD("Defs of lhs: ", getDefs(ws, lhs)) + lhs)]) + , AType consAdtType := cons.defInfo.atype.adt + , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- printlnExpD("Reachable defs (from constructor def): ", definitionsRel[rascalReachableDefs(ws, {cons.defined})]) + , isContainedIn(cons.defined, adt.defined)) { // Probably need to follow import paths here as well + defs += adt; + } + } else if (printlnExpD("Is data type []: ", rascalIsDataType(lhsType))) { + for (Define ctr:<_, _, _, constructorId(), _, defType(acons(lhsType, _, _))> <- definitionsRel[rascalReachableDefs(ws, getDefs(ws, lhs) + lhs)] + , Define adt:<_, _, _, dataId(), _, defType(lhsType)> <- definitionsRel[rascalReachableDefs(ws, {ctr.defined})] + , isContainedIn(ctr.defined, adt.defined)) { + defs += adt; + } } - throw "Unknown LHS type "; + return defs; } bool rascalAdtHasCommonKeywordField(str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) { @@ -324,10 +328,10 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , , , - // , + , // Any kind of keyword param; we'll decide which exactly later , - // , + , , // Module name declaration, where the cursor location is in the module header , @@ -346,8 +350,8 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { Cursor getDataFieldCursor(AType containerType, loc container) { - if (Define dt := rascalGetADTDefinition(ws, containerType, container) - , adtType := dt.defInfo.atype) { + for (Define dt <- rascalGetADTDefinitions(ws, containerType, container) + , adtType := dt.defInfo.atype) { if (rascalAdtHasCommonKeywordField(cursorName, dt)) { // Case 4 or 5 (or 0): common keyword field return cursor(dataCommonKeywordField(dt.defined), c, cursorName); @@ -378,7 +382,7 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { cur = getDataFieldCursor(containerType, container); } } - case {collectionField(), dataField(_), dataKeywordField(_), *_}: { //, dataCommonKeywordField(_), *_}: { + case {collectionField(), dataField(_), dataKeywordField(_), dataCommonKeywordField(_), *_}: { /* Possible cases: 0. We are on a field use/access (of either a data or collection field, in an expression/assignment/pattern(?)) 1. We are on a collection field diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index d6f375464..7e145b99b 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -235,10 +235,19 @@ set[loc] rascalGetKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorN return uses; } -set[loc] rascalGetKeywordArgs(none(), _) = {}; -set[loc] rascalGetKeywordArgs(\default(_, kwArgList), str cursorName) = +set[loc] rascalGetKeywordFormals((KeywordFormals) ``, str _) = {}; +set[loc] rascalGetKeywordFormals((KeywordFormals) ` <{KeywordFormal ","}+ keywordFormals>`, str cursorName) = + rascalGetKeywordFormalList(keywordFormals, cursorName); + +set[loc] rascalGetKeywordFormalList([{KeywordFormal ","}+] keywordFormals, str cursorName) = + { kwFormal.name.src + | kwFormal <- keywordFormals + , "" == cursorName}; + +set[loc] rascalGetKeywordArgs(none(), str _) = {}; +set[loc] rascalGetKeywordArgs(\default(_, keywordArgs), str cursorName) = { kwArg.name.src - | kwArg <- kwArgList + | kwArg <- keywordArgs , "" == cursorName}; set[loc] rascalGetKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { @@ -351,52 +360,47 @@ bool debug = false; &T printlnExpD(str msg, &T t) = debug ? printlnExp(msg, t) : t; -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataKeywordField(loc adtLoc), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { - set[loc] defs = {}; - set[loc] uses = {}; - - print("Defs: "); - iprintln(ws.defines); - - print("Use/defs: "); - iprintln(ws.useDef); - - set[loc] adtDefs = rascalGetOverloadedDefs(ws, {adtLoc}, mayOverloadF); - for (d <- adtDefs, dataTypeDef := ws.definitions[d], dataType := dataTypeDef.defInfo.atype) { - for (Define c:<_, _, _, constructorId(), _, _> <- ws.defines) { - for (u <- getUses(ws, c.defined) + c.defined) { - if (f <- sort(factsInvert(ws)[dataType], byLength) - , isContainedIn(u, f) - , m := parseModuleWithSpacesCached(f.top) - , /Tree t := m - , t.src?, t.src == f - , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { - print("Keyword locs (of use ): "); - iprintln(kwLocs); - - uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { + if (cursorKind is dataKeywordField || cursorKind is dataCommonKeywordField) { + set[loc] defs = {}; + set[loc] uses = {}; + + loc adtLoc = cursorKind.dataTypeDef; + set[loc] adtDefs = rascalGetOverloadedDefs(ws, {adtLoc}, mayOverloadF); + for (d <- adtDefs, dataTypeDef := ws.definitions[d], dataType := dataTypeDef.defInfo.atype) { + // Any field accesses of a common keyword + uses += {u | u <- getUses(ws, dataTypeDef.defined), just(dataType) !:= getFact(ws, u)}; + + for (Define c:<_, _, _, constructorId(), _, _> <- ws.defines) { + for (u <- getUses(ws, c.defined) + c.defined) { + if (f <- sort(factsInvert(ws)[dataType], byLength) + , isContainedIn(u, f) + , m := parseModuleWithSpacesCached(f.top) + , /Tree t := m + , t.src?, t.src == f + , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { + uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); + } } } - } - for (Define dt:<_, _, _, _, _, defType(acons(dataType, _, _))> <- ws.defines - , dt.idRole != dataId()) { - println("dt:
"); - if (f <- sort(factsInvert(ws)[dataType], desc(byLength)) + for (Define dt:<_, _, _, _, _, defType(acons(dataType, _, _))> <- ws.defines + , dt.idRole != dataId()) { + if (f <- sort(factsInvert(ws)[dataType], desc(byLength)) , isContainedIn(dt.defined, f)) { - m = parseModuleWithSpacesCached(f.top); - for (/Tree t := m - , t.src?, isContainedIn(t.src, f) - , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { - print("Keyword locs (of ): "); - iprintln(kwLocs); - - uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); + m = parseModuleWithSpacesCached(f.top); + for (/Tree t := m + , t.src?, isContainedIn(t.src, f) + , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { + uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); + } } } } + + return ; } - return ; + fail; } DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { @@ -446,22 +450,24 @@ Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (StructuredType) ` default Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, Tree _) = nothing(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Expression) `(<{Expression ","}* _> )`) { - kwArgLocs = rascalGetKeywordArgs(kwArgs, fieldName); - return kwArgLocs != {} ? just() : nothing(); -} +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Expression) `(<{Expression ","}* _> )`) = + just(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Pattern) `(<{Pattern ","}* _> )`) { - kwArgLocs = rascalGetKeywordArgs(kwArgs, fieldName); - return kwArgLocs != {} ? just() : nothing(); -} -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Variant) `(<{TypeArg ","}* _> )`) { - kwArgLocs = rascalGetKeywordArgs(kwArgs, fieldName); - return kwArgLocs != {} ? just() : nothing(); -} +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Pattern) `(<{Pattern ","}* _> )`) = + just(); + +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Variant) `(<{TypeArg ","}* _> )`) = + just(); + +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, d:(Declaration) ` data (<{KeywordFormal ","}+ kwFormalList>) = <{Variant "|"}+ _>;`) = + just(); + + +Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, d:(Declaration) ` data (<{KeywordFormal ","}+ kwFormalList>);`) = + just(); -default Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, Tree _) = nothing(); +default Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str _, Tree _) = nothing(); private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { set[loc] containerFacts = factsInvert(ws)[containerType]; From fa965c19fc7cbe2a044331d42177d0f262964ec4 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 26 Aug 2024 16:08:54 +0200 Subject: [PATCH 086/105] Find nested (field) definitions as well. --- .../src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 7e145b99b..4eabafbc5 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -192,13 +192,14 @@ set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun rel[loc d, loc scope] scopesOfDefUses = { | <- ws.useDef}; rel[loc def, loc scope] defAndTheirUseScopes = ws.defines + scopesOfDefUses; rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); + rel[loc scope, loc defined] scopeDefs = (ws.defines+); solve(overloadedDefs) { rel[loc from, loc to] reachableDefs = ident(overloadedDefs) // Start from all current defs o defAndTheirUseScopes // - Look up their scope and scopes of their uses o modulePaths // - Follow import/extend relations to reachable scopes - o ws.defines // - Find definitions in the reached scope + o scopeDefs // - Find definitions in the reached scope, and definitions within those definitions (transitively) ; overloadedDefs += {d From d2d8a7a4c75ce8d69156ee8111e3b111ac95a09c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 26 Aug 2024 16:09:49 +0200 Subject: [PATCH 087/105] Fix cursor at ADT field def. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 257a24c84..525caf5ba 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -405,7 +405,14 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { } case {def(), *_}: { // Cursor is at a definition - cur = cursor(def(), c, cursorName); + Define d = ws.definitions[c]; + if (d.idRole is fieldId + , Define adt: <_, _, _, dataId(), _, _> <- ws.defines + , isStrictlyContainedIn(c, adt.defined)) { + cur = getDataFieldCursor(adt.defInfo.atype, adt.defined); + } else { + cur = cursor(def(), c, cursorName); + } } case {use(), *_}: { set[loc] defs = getDefs(ws, c); From 98809e576f202a49d195364f5e0e3906a62a90bf Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 26 Aug 2024 16:14:14 +0200 Subject: [PATCH 088/105] Simplify cursor calculation/preloading. --- .../lang/rascal/lsp/refactor/Rename.rsc | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 525caf5ba..993676c80 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -294,12 +294,10 @@ bool rascalConsHasField(str fieldName, Define _:<_, _, _, constructorId(), _, de return false; } -tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { +Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { loc cursorLoc = cursorT.src; str cursorName = ""; - ws = preLoad(ws); - rel[loc field, loc container] fields = { | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) , just() := rascalGetFieldLocs(cursorName, t) @@ -370,16 +368,15 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { } loc c = min(locsContainingCursor.l); - Cursor cur = cursor(use(), |unknown:///|, ""); print("Locs containing cursor: "); iprintln(locsContainingCursor); switch (locsContainingCursor[c]) { case {moduleName(), *_}: { - cur = cursor(moduleName(), c, cursorName); + return cursor(moduleName(), c, cursorName); } case {keywordParam(), dataKeywordField(_), *_}: { if ({loc container} := keywords[c], just(containerType) := getFact(ws, container)) { - cur = getDataFieldCursor(containerType, container); + return getDataFieldCursor(containerType, container); } } case {collectionField(), dataField(_), dataKeywordField(_), dataCommonKeywordField(_), *_}: { @@ -397,9 +394,9 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if ((just(containerType) := maybeContainerType && rascalIsCollectionType(containerType)) || maybeContainerType == nothing()) { // Case 1 (or 0): collection field - cur = cursor(collectionField(), c, cursorName); + return cursor(collectionField(), c, cursorName); } else if (just(containerType) := maybeContainerType) { - cur = getDataFieldCursor(containerType, container); + return getDataFieldCursor(containerType, container); } } } @@ -409,9 +406,9 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (d.idRole is fieldId , Define adt: <_, _, _, dataId(), _, _> <- ws.defines , isStrictlyContainedIn(c, adt.defined)) { - cur = getDataFieldCursor(adt.defInfo.atype, adt.defined); + return getDataFieldCursor(adt.defInfo.atype, adt.defined); } else { - cur = cursor(def(), c, cursorName); + return cursor(def(), c, cursorName); } } case {use(), *_}: { @@ -420,7 +417,7 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (d <- defs, just(amodule(_)) := getFact(ws, d)) { // Cursor is at an import - cur = cursor(moduleName(), c, cursorName); + return cursor(moduleName(), c, cursorName); } else if (u <- ws.useDef<0> , isContainedIn(cursorLoc, u) , u.end > cursorLoc.end @@ -428,7 +425,7 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , {variableId()} !:= (ws.defines)[getDefs(ws, u)] ) { // Cursor is at a qualified name - cur = cursor(moduleName(), c, cursorName); + return cursor(moduleName(), c, cursorName); // } else if (/Tree t := parseModuleWithSpacesCached(cursorLoc.top) // , just() := getFieldLoc(cursorName, t) // , just(acons(adtType, _, _)) := getFact(ws, lhs) @@ -436,26 +433,22 @@ tuple[Cursor, WorkspaceInfo] rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { // , Define kwDef: <_, cursorName, _, keywordFormalId(), _, _> <- ws.defines // , isStrictlyContainedIn(kwDef.defined, dataDef.defined)) { // // Cursor is at a field use - // cur = cursor(dataField(), kwDef.defined, cursorName); + // return cursor(dataField(), kwDef.defined, cursorName); } else if (defines != {}) { // The cursor is at a use with corresponding definitions. - cur = cursor(use(), c, cursorName); + return cursor(use(), c, cursorName); } else if (just(at) := getFact(ws, c) , aparameter(cursorName, _) := at) { // The cursor is at a type parameter - cur = cursor(typeParam(), c, cursorName); + return cursor(typeParam(), c, cursorName); } } case {k}: { - cur = cursor(k, c, cursorName); + return cursor(k, c, cursorName); } } - if (cur.l.scheme == "unknown") throw unsupportedRename("Could not retrieve information for \'\' at ."); - - println("Cursor: "); - - return ; + throw unsupportedRename("Could not retrieve information for \'\' at ."); } private set[Name] rascalNameToEquivalentNames(str name) = { @@ -561,8 +554,11 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s } ); + // step("preloading minimal workspace information", 1); + ws = preLoad(ws); + // step("analyzing name at cursor", 1); - = rascalGetCursor(ws, cursorT); + cur = rascalGetCursor(ws, cursorT); // step("loading required type information", 1); if (!rascalIsFunctionLocal(ws, cur)) { @@ -599,7 +595,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s list[DocumentEdit] renames = [renamed(from, to) | <- getRenames(newName)]; return changes + renames; -}//, totalWork = 5); +}//, totalWork = 6); //// WORKAROUNDS From f709bf0985359cc6f633b9c1f1b71c131f85a3ee Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 26 Aug 2024 17:30:21 +0200 Subject: [PATCH 089/105] Resolve reachable definitions in two directions. --- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 4eabafbc5..e19d814a4 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -189,18 +189,21 @@ set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun "Initial defs are invalid overloads!"; map[loc file, loc scope] moduleScopePerFile = getModuleScopePerFile(ws); - rel[loc d, loc scope] scopesOfDefUses = { | <- ws.useDef}; - rel[loc def, loc scope] defAndTheirUseScopes = ws.defines + scopesOfDefUses; + rel[loc def, loc scope] defUseScopes = { | <- ws.useDef}; rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); - rel[loc scope, loc defined] scopeDefs = (ws.defines+); + rel[loc def, loc scope] defScopes = ws.defines+; + rel[loc scope, loc def] scopeDefs = invert(defScopes); + + rel[loc from, loc to] fromDefPaths = + (defScopes + defUseScopes) // 1. Look up scopes of defs and scopes of their uses + o modulePaths // 2. Follow import/extend relations to reachable scopes + o scopeDefs // 3. Find definitions in the reached scope, and definitions within those definitions (transitively) + ; + + rel[loc from, loc to] defPaths = fromDefPaths + invert(fromDefPaths); solve(overloadedDefs) { - rel[loc from, loc to] reachableDefs = - ident(overloadedDefs) // Start from all current defs - o defAndTheirUseScopes // - Look up their scope and scopes of their uses - o modulePaths // - Follow import/extend relations to reachable scopes - o scopeDefs // - Find definitions in the reached scope, and definitions within those definitions (transitively) - ; + rel[loc from, loc to] reachableDefs = ident(overloadedDefs) o defPaths; overloadedDefs += {d | loc d <- reachableDefs<1> From 949c609f0b53d775c5d4943d9fb014bc9965b00e Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 27 Aug 2024 17:10:11 +0200 Subject: [PATCH 090/105] Re-use more generic field machinery. --- .../lang/rascal/lsp/refactor/Rename.rsc | 62 +++++------ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 103 ++++++++---------- .../lang/rascal/tests/rename/Fields.rsc | 8 +- .../lang/rascal/tests/rename/Variables.rsc | 7 ++ 4 files changed, 88 insertions(+), 92 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 993676c80..df3b5cd02 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -226,7 +226,8 @@ private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTex computeTextEdits(ws, parseModuleWithSpacesCached(moduleLoc), defs, uses, name); private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { - set[str] names = {definitions[l].id | l <- defs, definitions[l]?}; + if (l <- defs, !definitions[l]?) return false; + set[str] names = {definitions[l].id | l <- defs}; if (size(names) > 1) return false; map[loc, Define] potentialOverloadDefinitions = (l: d | l <- definitions, d := definitions[l], d.id in names); @@ -273,25 +274,24 @@ private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, AType lhsType, loc return defs; } -bool rascalAdtHasCommonKeywordField(str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) { - if (defInfo.commonKeywordFields?) { - for ((KeywordFormal) ` = ` <- defInfo.commonKeywordFields, "" == fieldName) { - return true; - } +Maybe[AType] rascalAdtCommonKeywordFieldType(WorkspaceInfo ws, str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) { + if (defInfo.commonKeywordFields? + , kwf:(KeywordFormal) ` = ` <- defInfo.commonKeywordFields + , "" == fieldName) { + if (ft:just(_) := getFact(ws, kwf.src)) return ft; + throw "Unknown field type for "; } - return false; + return nothing(); } -bool rascalConsHasKeywordField(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, _, kwFields))>) { - for (kwField(_, fieldName, _) <- kwFields) return true; - return false; +Maybe[AType] rascalConsKeywordFieldType(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, _, kwFields))>) { + if (kwField(fieldType, fieldName, _) <- kwFields) return just(fieldType); + return nothing(); } -bool rascalConsHasField(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, fields, _))>) { - for (field <- fields) { - if (field.alabel == fieldName) return true; - } - return false; +Maybe[AType] rascalConsFieldType(str fieldName, Define _:<_, _, _, constructorId(), _, defType(acons(_, fields, _))>) { + if (field <- fields, field.alabel == fieldName) return just(field); + return nothing(); } Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { @@ -300,13 +300,13 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { rel[loc field, loc container] fields = { | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) - , just() := rascalGetFieldLocs(cursorName, t) + , just() := rascalGetFieldLocs(cursorName, t) , loc fieldLoc <- fieldLocs }; rel[loc kw, loc container] keywords = { | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) - , just() := rascalGetKeywordLocs(cursorName, t) + , just() := rascalGetKeywordLocs(cursorName, t) , loc kwLoc <- kwLocs }; @@ -324,12 +324,12 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , // Any kind of field; we'll decide which exactly later , - , - , - , + , + , + , // Any kind of keyword param; we'll decide which exactly later - , - , + , + , , // Module name declaration, where the cursor location is in the module header , @@ -350,21 +350,21 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { Cursor getDataFieldCursor(AType containerType, loc container) { for (Define dt <- rascalGetADTDefinitions(ws, containerType, container) , adtType := dt.defInfo.atype) { - if (rascalAdtHasCommonKeywordField(cursorName, dt)) { + if (just(fieldType) := rascalAdtCommonKeywordFieldType(ws, cursorName, dt)) { // Case 4 or 5 (or 0): common keyword field - return cursor(dataCommonKeywordField(dt.defined), c, cursorName); + return cursor(dataCommonKeywordField(dt.defined, fieldType), c, cursorName); } else if (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { - if (rascalConsHasKeywordField(cursorName, d)) { + if (just(fieldType) := rascalConsKeywordFieldType(cursorName, d)) { // Case 3 (or 0): keyword field - return cursor(dataKeywordField(dt.defined), c, cursorName); - } else if (rascalConsHasField(cursorName, d)) { + return cursor(dataKeywordField(dt.defined, fieldType), c, cursorName); + } else if (just(fieldType) := rascalConsFieldType(cursorName, d)) { // Case 2 (or 0): positional field - return cursor(dataField(dt.defined), c, cursorName); + return cursor(dataField(dt.defined, fieldType), c, cursorName); } } } - throw "Cannot derive data field information for at "; + throw illegalRename("Cannot rename \'\'; it is not defined in this workspace", {definitionsOutsideWorkspace(getDefs(ws, cursorLoc))}); } loc c = min(locsContainingCursor.l); @@ -374,12 +374,12 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { case {moduleName(), *_}: { return cursor(moduleName(), c, cursorName); } - case {keywordParam(), dataKeywordField(_), *_}: { + case {keywordParam(), dataKeywordField(_, _), *_}: { if ({loc container} := keywords[c], just(containerType) := getFact(ws, container)) { return getDataFieldCursor(containerType, container); } } - case {collectionField(), dataField(_), dataKeywordField(_), dataCommonKeywordField(_), *_}: { + case {collectionField(), dataField(_, _), dataKeywordField(_, _), dataCommonKeywordField(_, _), *_}: { /* Possible cases: 0. We are on a field use/access (of either a data or collection field, in an expression/assignment/pattern(?)) 1. We are on a collection field diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index e19d814a4..ccf4da910 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -54,9 +54,9 @@ data CursorKind | def() | typeParam() | collectionField() - | dataField(loc dataTypeDef) - | dataKeywordField(loc dataTypeDef) - | dataCommonKeywordField(loc dataTypeDef) + | dataField(loc dataTypeDef, AType fieldType) + | dataKeywordField(loc dataTypeDef, AType fieldType) + | dataCommonKeywordField(loc dataTypeDef, AType fieldType) | keywordParam() | moduleName() ; @@ -334,7 +334,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLo return ; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, AType fieldType), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { set[loc] initialDefs = {}; if (cursorLoc in ws.useDef<0>) { // println("Data field is a use"); @@ -372,32 +372,16 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc loc adtLoc = cursorKind.dataTypeDef; set[loc] adtDefs = rascalGetOverloadedDefs(ws, {adtLoc}, mayOverloadF); for (d <- adtDefs, dataTypeDef := ws.definitions[d], dataType := dataTypeDef.defInfo.atype) { - // Any field accesses of a common keyword - uses += {u | u <- getUses(ws, dataTypeDef.defined), just(dataType) !:= getFact(ws, u)}; - - for (Define c:<_, _, _, constructorId(), _, _> <- ws.defines) { - for (u <- getUses(ws, c.defined) + c.defined) { - if (f <- sort(factsInvert(ws)[dataType], byLength) - , isContainedIn(u, f) - , m := parseModuleWithSpacesCached(f.top) - , /Tree t := m - , t.src?, t.src == f - , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { - uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); - } - } + for (Define _:<_, _, _, constructorId(), _, defType(AType consType)> <- ws.defines) { + = rascalGetFieldDefsUses(ws, consType, cursorKind.fieldType, cursorName); + defs += consDefs; + uses += consUses; } - for (Define dt:<_, _, _, _, _, defType(acons(dataType, _, _))> <- ws.defines - , dt.idRole != dataId()) { - if (f <- sort(factsInvert(ws)[dataType], desc(byLength)) - , isContainedIn(dt.defined, f)) { - m = parseModuleWithSpacesCached(f.top); - for (/Tree t := m - , t.src?, isContainedIn(t.src, f) - , just(<_, kwLocs>) := rascalGetKeywordLocs(cursorName, t)) { - uses += kwLocs + printlnExpD("Uses of kw locs: ", getUses(ws, kwLocs)); - } - } + for (Define _:<_, _, _, IdRole idRole, _, defType(acons(dataType, _, _))> <- ws.defines + , idRole != dataId()) { + = rascalGetFieldDefsUses(ws, dataType, cursorKind.fieldType, cursorName); + defs += adtDefs; + uses += adtUses; } } @@ -428,50 +412,49 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cu } } -Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Expression) `.`) = - just() when fieldName == ""; +Maybe[tuple[loc, set[loc], bool]] rascalGetFieldLocs(str fieldName, (Expression) `.`) = + just() when fieldName == ""; -Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Assignable) `.`) = - just() when fieldName == ""; +Maybe[tuple[loc, set[loc], bool]] rascalGetFieldLocs(str fieldName, (Assignable) `.`) = + just() when fieldName == ""; -Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Expression) `\< <{Field ","}+ fields> \>`) { +Maybe[tuple[loc, set[loc], bool]] rascalGetFieldLocs(str fieldName, (Expression) `\< <{Field ","}+ fields> \>`) { fieldLocs = {field.src | field <- fields , field is name , "" == fieldName }; - return fieldLocs != {} ? just() : nothing(); + return fieldLocs != {} ? just() : nothing(); } -Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (Expression) `[ = ]`) = - just() when fieldName == ""; +Maybe[tuple[loc, set[loc], bool]] rascalGetFieldLocs(str fieldName, (Expression) `[ = ]`) = + just() when fieldName == ""; -Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, (StructuredType) `[<{TypeArg ","}+ args>]`) { +Maybe[tuple[loc, set[loc], bool]] rascalGetFieldLocs(str fieldName, (StructuredType) `[<{TypeArg ","}+ args>]`) { fieldLocs = {name.src | (TypeArg) ` ` <- args, fieldName == ""}; - return fieldLocs != {} ? just() : nothing(); + return fieldLocs != {} ? just() : nothing(); } -default Maybe[tuple[loc, set[loc]]] rascalGetFieldLocs(str fieldName, Tree _) = nothing(); +default Maybe[tuple[loc, set[loc], bool]] rascalGetFieldLocs(str fieldName, Tree _) = nothing(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Expression) `(<{Expression ","}* _> )`) = - just(); +Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str fieldName, (Expression) `(<{Expression ","}* _> )`) = + just(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Pattern) `(<{Pattern ","}* _> )`) = - just(); +Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str fieldName, (Pattern) `(<{Pattern ","}* _> )`) = + just(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, (Variant) `(<{TypeArg ","}* _> )`) = - just(); +Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str fieldName, (Variant) `(<{TypeArg ","}* _> )`) = + just(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, d:(Declaration) ` data (<{KeywordFormal ","}+ kwFormalList>) = <{Variant "|"}+ _>;`) = - just(); +Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str fieldName, d:(Declaration) ` data (<{KeywordFormal ","}+ kwFormalList>) = <{Variant "|"}+ _>;`) = + just(); +Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str fieldName, d:(Declaration) ` data (<{KeywordFormal ","}+ kwFormalList>);`) = + just(); -Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str fieldName, d:(Declaration) ` data (<{KeywordFormal ","}+ kwFormalList>);`) = - just(); - -default Maybe[tuple[loc, set[loc]]] rascalGetKeywordLocs(str _, Tree _) = nothing(); +default Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str _, Tree _) = nothing(); private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { set[loc] containerFacts = factsInvert(ws)[containerType]; @@ -481,12 +464,16 @@ private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, AType container set[loc] uses = {}; for (file <- factsByModule.file) { fileFacts = factsByModule[file]; - for (/Tree t := parseModuleWithSpacesCached(file), just() := rascalGetFieldLocs(cursorName, t)) { - if ((StructuredType) `[<{TypeArg ","}+ _>]` := t - , at := ws.facts[t.src] - , containerType.elemType? - , containerType.elemType == at.elemType) { - defs += {f | f <- fields, fieldType == ws.facts[f]}; + for (/Tree t := parseModuleWithSpacesCached(file) + , just() := rascalGetFieldLocs(cursorName, t) || just() := rascalGetKeywordLocs(cursorName, t)) { + if((StructuredType) `[<{TypeArg ","}+ _>]` := t) { + if(at := ws.facts[t.src] + , containerType.elemType? + , containerType.elemType == at.elemType) { + defs += {f | f <- fields, just(fieldType) := getFact(ws, f)}; + } + } else if (isDef) { + defs += fields; } else if (any(f <- fileFacts, isContainedIn(f, lhs))) { uses += fields; } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 01fe95dde..0f66d1597 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -47,6 +47,8 @@ test bool commonKeywordField() = testRenameOccurrences({0, 1, 2}, " ", decls = "data D(int foo = 0, int baz = 0) = d();" ); +// Flaky. Fix for non-determinism in typepal, upcoming in future release of Rascal (Core) +// https://github.com/usethesource/typepal/commit/55456edcc52653e42d7f534a5412147b01b68c29 test bool multipleConstructorField() = testRenameOccurrences({0, 1, 2}, " 'x = d(1, 2); 'y = x.foo; @@ -137,7 +139,7 @@ test bool dataTypeReusedName() = testRenameOccurrences({ byText("Scratch2", " 'data Foo = g(); ", {}) -}); +}, oldName = "Foo", newName = "Bar"); test bool dataFieldReusedName() = testRenameOccurrences({ byText("Scratch1", " @@ -219,14 +221,14 @@ test bool tupleField() = testRenameOccurrences({0, 1}, " "); // We would prefer an illegalRename exception here -@expected{unsupportedRename} +@expected{illegalRename} test bool builtinFieldSimpleType() = testRename(" 'loc l = |unknown:///|; 'f = l.top; ", oldName = "top", newName = "summit"); // We would prefer an illegalRename exception here -@expected{unsupportedRename} +@expected{illegalRename} test bool builtinFieldCollectionType() = testRename(" 'loc l = |unknown:///|; 'f = l.ls; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc index ae87d9dd5..ed1408305 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Variables.rsc @@ -197,3 +197,10 @@ test bool multiModuleVar() = testRenameOccurrences({ '}" , {0}) }); + +test bool unrelatedVar() = testRenameOccurrences({ + byText("Module1", "int foo = 8;", {0}) + , byText("Module2", "import Module1; + 'int foo = 2; + 'int baz = foo;", {}) +}); From df99b8286e89389df7749f048b7832b2a1125a11 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 28 Aug 2024 17:11:39 +0200 Subject: [PATCH 091/105] Only rename definitions with the same role. --- .../rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 9 +++++++-- .../src/main/rascal/lang/rascal/tests/rename/Types.rsc | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index ccf4da910..e67995c33 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -184,15 +184,20 @@ set[loc] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { set[loc] overloadedDefs = defs; - // Pre-condition + set[IdRole] roles = toRel(ws.definitions)[defs].idRole; + + // Pre-conditions + assert size(roles) == 1: + "Initial defs are of different roles!"; assert mayOverloadF(overloadedDefs, ws.definitions): "Initial defs are invalid overloads!"; + IdRole role = getFirstFrom(roles); map[loc file, loc scope] moduleScopePerFile = getModuleScopePerFile(ws); rel[loc def, loc scope] defUseScopes = { | <- ws.useDef}; rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); rel[loc def, loc scope] defScopes = ws.defines+; - rel[loc scope, loc def] scopeDefs = invert(defScopes); + rel[loc scope, loc defined] scopeDefs = (ws.defines)[role]+; rel[loc from, loc to] fromDefPaths = (defScopes + defUseScopes) // 1. Look up scopes of defs and scopes of their uses diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc index e47c46f1c..d98f64a5e 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Types.rsc @@ -165,3 +165,13 @@ test bool dataTypesInIIModuleStructure() = testRenameOccurrences({ 'bool func(Foo foo) = foo == f();", {0}), byText("RightUser", "import RightExtender; 'bool func(Foo foo) = foo == g();", {}) }, oldName = "Foo", newName = "Bar"); + +test bool sameIdRoleOnly() = testRenameOccurrences({ + byText("A", "data foo = f();", {}) + , byText("B", "extend A; + 'data foo = g();", {}) + , byText("C", "extend B; + 'int foo = 8;",{0}) + , byText("D", "import C; + 'int baz = C::foo + 1;", {0}) +}); From 5be976b96b8d590699123df9c4de96366adca929 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 29 Aug 2024 14:13:25 +0200 Subject: [PATCH 092/105] Fix iteration in presence of multiple definitions. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index df3b5cd02..3cd488147 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -353,7 +353,9 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (just(fieldType) := rascalAdtCommonKeywordFieldType(ws, cursorName, dt)) { // Case 4 or 5 (or 0): common keyword field return cursor(dataCommonKeywordField(dt.defined, fieldType), c, cursorName); - } else if (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { + } + + for (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { if (just(fieldType) := rascalConsKeywordFieldType(cursorName, d)) { // Case 3 (or 0): keyword field return cursor(dataKeywordField(dt.defined, fieldType), c, cursorName); From 0f28d3f71be0acfb47433debcce96c64d70cbb03 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 29 Aug 2024 14:13:54 +0200 Subject: [PATCH 093/105] Support skipping cursors in multi-module tests. --- .../main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 85ca15e05..8e3b7225c 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -52,8 +52,8 @@ import util::Reflective; //// Fixtures and utility functions -data TestModule = byText(str name, str body, set[int] nameOccs, str newName = name) - | byLoc(loc file, set[int] nameOccs, str newName = name); +data TestModule = byText(str name, str body, set[int] nameOccs, str newName = name, set[int] skipCursors = {}) + | byLoc(loc file, set[int] nameOccs, str newName = name, set[int] skipCursors = {}); private list[DocumentEdit] sortEdits(list[DocumentEdit] edits) = [sortChanges(e) | e <- edits]; @@ -91,7 +91,7 @@ bool expectEq(&T expected, &T actual, str epilogue = "") { bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str newName = "bar") { bool success = true; - for (mm <- modules, cursorOcc <- mm.nameOccs) { + for (mm <- modules, cursorOcc <- (mm.nameOccs - mm.skipCursors)) { str testName = "Test"; loc testDir = |memory://tests/rename/|; @@ -103,7 +103,7 @@ bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str new } pcfg = getTestPathConfig(testDir); - modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.nameOccs, newName = m.newName))}; + modulesByLocation = {mByLoc | m <- modules, mByLoc := (m is byLoc ? m : byLoc(storeTestModule(testDir, m.name, m.body), m.nameOccs, newName = m.newName, skipCursors = m.skipCursors))}; cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == mm.name][0], oldName, cursorOcc); println("Renaming \'\' from "); From 2a6108d2f4ba3281bf1aa34966a0bd3d05e46e45 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 29 Aug 2024 16:40:36 +0200 Subject: [PATCH 094/105] Resolve fields in data definitions. --- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index e67995c33..0f2a94b1f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -197,7 +197,10 @@ set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun rel[loc def, loc scope] defUseScopes = { | <- ws.useDef}; rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); rel[loc def, loc scope] defScopes = ws.defines+; - rel[loc scope, loc defined] scopeDefs = (ws.defines)[role]+; + rel[loc scope, loc defined] scopeDefs = + (ws.defines)+ // Follow definition scopes ... + o ((ws.defines)[role]) // Until we arrive at a definition with the same role ... + ; rel[loc from, loc to] fromDefPaths = (defScopes + defUseScopes) // 1. Look up scopes of defs and scopes of their uses From 2d1bbf9cfd2217b338246912d98a25d8c0a957c8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 29 Aug 2024 17:18:55 +0200 Subject: [PATCH 095/105] Fix existing field detection. --- .../main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 12 ++++-------- .../main/rascal/lang/rascal/tests/rename/Fields.rsc | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 3cd488147..ffeaf3a48 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -99,14 +99,10 @@ private set[IllegalRenameReason] rascalCheckCausesDoubleDeclarations(WorkspaceIn }; rel[loc old, loc new] doubleFieldDeclarations = { - | Define _: <- newDefs - , loc cD <- currentDefs - , ws.definitions[cD]? - , Define _: := ws.definitions[cD] - , fL <- ws.facts, at := ws.facts[fL] - , acons(aadt(_, _, _), _, _) := at - , isStrictlyContainedIn(cD, fL) - , isStrictlyContainedIn(nD, fL) + | Define _: <- toRel(ws.definitions)[currentDefs] + // The scope of a field def is the surrounding data def + , loc dataDef <- rascalGetOverloadedDefs(ws, {curFieldScope}, rascalMayOverloadSameName) + , loc nD <- (newDefs)[fieldId()] & (ws.defines)[fieldId(), dataDef] }; rel[loc old, loc new] doubleTypeParamDeclarations = { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index 0f66d1597..efe092a65 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -112,7 +112,7 @@ test bool complexDataType() = testRenameOccurrences({0, 1}, , oldName = "sourceFiles", newName = "sources"); test bool crossModuleConstructorField() = testRenameOccurrences({ - byText("Foo", "data D = a(int foo) | b(int bar);", {0}), + byText("Foo", "data D = a(int foo) | b(int baz);", {0}), byText("Main", " 'import Foo; 'void main() { From 18a18bf8d4e75fa87ba9151fd40ce1cbe66f6e41 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 30 Aug 2024 13:13:22 +0200 Subject: [PATCH 096/105] Consider overloaded/reachable ADT defs only. --- .../lang/rascal/lsp/refactor/Rename.rsc | 36 ++++++++++--------- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 1 + 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index ffeaf3a48..fc6381070 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -251,23 +251,26 @@ private default bool rascalIsFunctionLocal(_, _) = false; private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, AType lhsType, loc lhs) { rel[loc, Define] definitionsRel = toRel(ws.definitions); - set[Define] defs = {}; - if (printlnExpD("Is constructor type []: ", rascalIsConstructorType(lhsType))) { - for (Define cons: <_, _, _, constructorId(), _, _> <- printlnExpD("Reachable defs (from lhs): ", definitionsRel[rascalReachableDefs(ws, printlnExpD("Defs of lhs: ", getDefs(ws, lhs)) + lhs)]) - , AType consAdtType := cons.defInfo.atype.adt - , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- printlnExpD("Reachable defs (from constructor def): ", definitionsRel[rascalReachableDefs(ws, {cons.defined})]) - , isContainedIn(cons.defined, adt.defined)) { // Probably need to follow import paths here as well - defs += adt; - } - } else if (printlnExpD("Is data type []: ", rascalIsDataType(lhsType))) { - for (Define ctr:<_, _, _, constructorId(), _, defType(acons(lhsType, _, _))> <- definitionsRel[rascalReachableDefs(ws, getDefs(ws, lhs) + lhs)] - , Define adt:<_, _, _, dataId(), _, defType(lhsType)> <- definitionsRel[rascalReachableDefs(ws, {ctr.defined})] - , isContainedIn(ctr.defined, adt.defined)) { - defs += adt; - } + set[loc] fromDefs = (ws.definitions[lhs]? || lhs in ws.useDef<1>) + ? {lhs} + : getDefs(ws, lhs) + ; + + if (rascalIsConstructorType(lhsType)) { + return {adt + | loc cD <- rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) + , Define cons: <_, _, _, constructorId(), _, _> := ws.definitions[cD] + , AType consAdtType := cons.defInfo.atype.adt + , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- definitionsRel[rascalReachableDefs(ws, {cons.defined})] + }; + } else if (rascalIsDataType(lhsType)) { + return {adt + | set[loc] overloads := rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) + , Define adt: <_, _, _, dataId(), _, defType(lhsType)> <- definitionsRel[rascalReachableDefs(ws, overloads)] + }; } - return defs; + return {}; } Maybe[AType] rascalAdtCommonKeywordFieldType(WorkspaceInfo ws, str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) { @@ -362,7 +365,8 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { } } - throw illegalRename("Cannot rename \'\'; it is not defined in this workspace", {definitionsOutsideWorkspace(getDefs(ws, cursorLoc))}); + set[loc] fromDefs = cursorLoc in ws.useDef<1> ? {cursorLoc} : getDefs(ws, cursorLoc); + throw illegalRename("Cannot rename \'\'; it is not defined in this workspace", {definitionsOutsideWorkspace(fromDefs)}); } loc c = min(locsContainingCursor.l); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 0f2a94b1f..d0b4ce64a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -182,6 +182,7 @@ set[loc] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { } set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { + if (defs == {}) return {}; set[loc] overloadedDefs = defs; set[IdRole] roles = toRel(ws.definitions)[defs].idRole; From 216acc1f7e0c53c4cc6fdd422904f493072f2013 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 30 Aug 2024 15:57:14 +0200 Subject: [PATCH 097/105] Find fields to rename in reachable scopes only. --- rascal-lsp/pom.xml | 4 +- .../lang/rascal/lsp/refactor/Rename.rsc | 21 +++--- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 68 ++++++++++++------- .../lang/rascal/tests/rename/Fields.rsc | 4 +- .../lang/rascal/tests/rename/TestUtils.rsc | 6 +- 5 files changed, 60 insertions(+), 43 deletions(-) diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml index dfe3da7d6..998a5c86f 100644 --- a/rascal-lsp/pom.xml +++ b/rascal-lsp/pom.xml @@ -68,12 +68,12 @@ org.rascalmpl rascal-core - 0.12.3 + 0.12.4 org.rascalmpl typepal - 0.13.4 + 0.14.0 diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index fc6381070..ea35815c0 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -248,7 +248,7 @@ private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _) private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; private default bool rascalIsFunctionLocal(_, _) = false; -private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, AType lhsType, loc lhs) { +private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, loc lhs) { rel[loc, Define] definitionsRel = toRel(ws.definitions); set[loc] fromDefs = (ws.definitions[lhs]? || lhs in ws.useDef<1>) @@ -256,6 +256,7 @@ private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, AType lhsType, loc : getDefs(ws, lhs) ; + AType lhsType = ws.facts[lhs]; if (rascalIsConstructorType(lhsType)) { return {adt | loc cD <- rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) @@ -346,8 +347,8 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { // iprintln(ws.facts); - Cursor getDataFieldCursor(AType containerType, loc container) { - for (Define dt <- rascalGetADTDefinitions(ws, containerType, container) + Cursor getDataFieldCursor(loc container) { + for (Define dt <- rascalGetADTDefinitions(ws, container) , adtType := dt.defInfo.atype) { if (just(fieldType) := rascalAdtCommonKeywordFieldType(ws, cursorName, dt)) { // Case 4 or 5 (or 0): common keyword field @@ -370,15 +371,15 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { } loc c = min(locsContainingCursor.l); - print("Locs containing cursor: "); - iprintln(locsContainingCursor); + // print("Locs containing cursor: "); + // iprintln(locsContainingCursor); switch (locsContainingCursor[c]) { case {moduleName(), *_}: { return cursor(moduleName(), c, cursorName); } case {keywordParam(), dataKeywordField(_, _), *_}: { - if ({loc container} := keywords[c], just(containerType) := getFact(ws, container)) { - return getDataFieldCursor(containerType, container); + if ({loc container} := keywords[c]) { + return getDataFieldCursor(container); } } case {collectionField(), dataField(_, _), dataKeywordField(_, _), dataCommonKeywordField(_, _), *_}: { @@ -397,8 +398,8 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { || maybeContainerType == nothing()) { // Case 1 (or 0): collection field return cursor(collectionField(), c, cursorName); - } else if (just(containerType) := maybeContainerType) { - return getDataFieldCursor(containerType, container); + } else { + return getDataFieldCursor(container); } } } @@ -408,7 +409,7 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (d.idRole is fieldId , Define adt: <_, _, _, dataId(), _, _> <- ws.defines , isStrictlyContainedIn(c, adt.defined)) { - return getDataFieldCursor(adt.defInfo.atype, adt.defined); + return getDataFieldCursor(adt.defined); } else { return cursor(def(), c, cursorName); } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index d0b4ce64a..fa506c59a 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -170,6 +170,22 @@ rel[loc from, loc to] rascalGetTransitiveReflexiveModulePaths(WorkspaceInfo ws) @memo{maximumSize=1, minutes=5} rel[loc from, loc to] rascalGetTransitiveReflexiveScopes(WorkspaceInfo ws) = toRel(ws.scopes)*; +@memo{maximumSize=10, minutes=5} +set[loc] rascalReachableModules(WorkspaceInfo ws, set[loc] froms) { + rel[loc from, loc scope] fromScopes = {}; + for (from <- froms) { + if (scope <- ws.scopes<1>, isContainedIn(from, scope)) { + fromScopes += ; + } + } + rel[loc from, loc modScope] reachable = + fromScopes + o rascalGetTransitiveReflexiveScopes(ws) + o rascalGetTransitiveReflexiveModulePaths(ws); + + return {s.top | s <- reachable.modScope}; +} + set[loc] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); rel[loc from, loc to] scopes = rascalGetTransitiveReflexiveScopes(ws); @@ -273,8 +289,8 @@ set[loc] rascalGetKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorNa , isStrictlyContainedIn(consDef.defined, dataDef.defined) ) { if (AType fieldType := ws.definitions[d].defInfo.atype) { - // println("Def: "); - if (<{}, consUses, _> := rascalGetFieldDefsUses(ws, dataDef.defInfo.atype, fieldType, cursorName)) { + set[loc] reachableModules = rascalReachableModules(ws, {d}); + if (<{}, consUses, _> := rascalGetFieldDefsUses(ws, reachableModules, dataDef.defInfo.atype, fieldType, cursorName)) { uses += consUses; } } else { @@ -346,16 +362,13 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLo DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, AType fieldType), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { set[loc] initialDefs = {}; if (cursorLoc in ws.useDef<0>) { - // println("Data field is a use"); initialDefs = getDefs(ws, cursorLoc); } else if (cursorLoc in ws.defines) { - // println("Data field is a def"); initialDefs = {cursorLoc}; } else if (just(AType adtType) := getFact(ws, cursorLoc)) { - // println("Data field is something else"); initialDefs = { kwDef.defined - | Define dataDef: <_, _, _, dataId(), _, defType(adtType)> <- ws.defines + | Define dataDef: <_, _, _, dataId(), _, defType(adtType)> <- rascalGetADTDefinitions(ws, cursorLoc) , Define kwDef: <_, _, cursorName, keywordFormalId(), _, _> <- ws.defines , isStrictlyContainedIn(kwDef.defined, dataDef.defined) }; @@ -378,20 +391,21 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc set[loc] defs = {}; set[loc] uses = {}; - loc adtLoc = cursorKind.dataTypeDef; - set[loc] adtDefs = rascalGetOverloadedDefs(ws, {adtLoc}, mayOverloadF); - for (d <- adtDefs, dataTypeDef := ws.definitions[d], dataType := dataTypeDef.defInfo.atype) { - for (Define _:<_, _, _, constructorId(), _, defType(AType consType)> <- ws.defines) { - = rascalGetFieldDefsUses(ws, consType, cursorKind.fieldType, cursorName); - defs += consDefs; - uses += consUses; - } - for (Define _:<_, _, _, IdRole idRole, _, defType(acons(dataType, _, _))> <- ws.defines - , idRole != dataId()) { - = rascalGetFieldDefsUses(ws, dataType, cursorKind.fieldType, cursorName); - defs += adtDefs; - uses += adtUses; - } + set[loc] adtDefs = rascalGetOverloadedDefs(ws, {cursorKind.dataTypeDef}, mayOverloadF); + rel[loc, Define] definitionsRel = toRel(ws.definitions); + set[Define] reachableDefs = definitionsRel[rascalReachableDefs(ws, adtDefs)]; + set[loc] reachableModules = rascalReachableModules(ws, reachableDefs.defined); + + for (Define _:<_, _, _, constructorId(), _, defType(AType consType)> <- reachableDefs) { + = rascalGetFieldDefsUses(ws, reachableModules, consType, cursorKind.fieldType, cursorName); + defs += ds; + uses += us; + } + for (Define _:<_, _, _, IdRole idRole, _, defType(acons(AType dataType, _, _))> <- reachableDefs + , idRole != dataId()) { + += rascalGetFieldDefsUses(ws, reachableModules, dataType, cursorKind.fieldType, cursorName); + defs += ds; + uses += us; } return ; @@ -406,16 +420,20 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cu AType cursorType = ws.facts[cursorLoc]; list[loc] factLocsSortedBySize = sort(domain(ws.facts), byLength); - if (l <- factLocsSortedBySize, isStrictlyContainedIn(cursorLoc, l), collUseType := ws.facts[l], rascalIsCollectionType(collUseType), collUseType.elemType is atypeList) { + if (l <- factLocsSortedBySize + , isStrictlyContainedIn(cursorLoc, l) + , collUseType := ws.facts[l] + , rascalIsCollectionType(collUseType) + , collUseType.elemType is atypeList) { // We are at a collection definition site - return rascalGetFieldDefsUses(ws, collUseType, cursorType, cursorName); + return rascalGetFieldDefsUses(ws, rascalReachableModules(ws, {cursorLoc}), collUseType, cursorType, cursorName); } // We can find the collection type by looking for the first use to the left of the cursor that has a collection type lrel[loc use, loc def] usesToLeft = reverse(sort({ | <- ws.useDef, isSameFile(u, cursorLoc), u.offset < cursorLoc.offset})); if (<_, d> <- usesToLeft, define := ws.definitions[d], defType(AType collDefType) := define.defInfo, rascalIsCollectionType(collDefType)) { // We are at a use site, where the field element type is wrapped in a `aset` of `alist` constructor - return rascalGetFieldDefsUses(ws, collDefType, isTupleField(cursorType) ? cursorType.elmType : cursorType, cursorName); + return rascalGetFieldDefsUses(ws, rascalReachableModules(ws, {define.defined}), collDefType, isTupleField(cursorType) ? cursorType.elmType : cursorType, cursorName); } else { throw unsupportedRename("Could not find a collection definition corresponding to the field at the cursor."); } @@ -465,8 +483,8 @@ Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str fieldName, d:(Declara default Maybe[tuple[loc, set[loc], bool]] rascalGetKeywordLocs(str _, Tree _) = nothing(); -private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, AType containerType, AType fieldType, str cursorName) { - set[loc] containerFacts = factsInvert(ws)[containerType]; +private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, set[loc] reachableModules, AType containerType, AType fieldType, str cursorName) { + set[loc] containerFacts = {f | f <- factsInvert(ws)[containerType], f.top in reachableModules}; rel[loc file, loc u] factsByModule = groupBy(containerFacts, loc(loc l) { return l.top; }); set[loc] defs = {}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index efe092a65..fe28bcbf3 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -146,7 +146,7 @@ test bool dataFieldReusedName() = testRenameOccurrences({ 'data Foo = f(int foo); ", {0}), byText("Scratch2", " - 'data Foo = g(int foo); + 'data Foo = f(int foo); ", {}) }); @@ -155,7 +155,7 @@ test bool dataKeywordFieldReusedName() = testRenameOccurrences({ 'data Foo = f(int foo = 0); ", {0}), byText("Scratch2", " - 'data Foo = g(int foo = 0); + 'data Foo = f(int foo = 0); ", {}) }); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index 8e3b7225c..a3fdfa0fa 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -137,8 +137,6 @@ bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str new if (!expectEq(expectedEditsPerModule, editsPerModule, epilogue = "Rename from cursor failed:")) { success = false; - println("Unexpected edits: "); - iprintln(edits); } for (src <- pcfg.srcs) { @@ -282,8 +280,8 @@ private set[int] extractRenameOccurrences(loc moduleFileName, list[DocumentEdit] oldNameOccurrences += n.src; } - print("All locations of \'\': "); - iprintln(sort(oldNameOccurrences, byOffset)); + // print("All locations of \'\': "); + // iprintln(sort(oldNameOccurrences, byOffset)); if ([changed(_, replaces)] := edits) { set[int] idx = {}; From 86a359aa60cbf635aa0f394bb601515e0df39acc Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 30 Aug 2024 17:11:13 +0200 Subject: [PATCH 098/105] Optimize repeated code and clean up. --- .../lang/rascal/lsp/refactor/Rename.rsc | 58 +++++-------------- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 28 +++++---- 2 files changed, 32 insertions(+), 54 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index ea35815c0..c45a89161 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -99,7 +99,7 @@ private set[IllegalRenameReason] rascalCheckCausesDoubleDeclarations(WorkspaceIn }; rel[loc old, loc new] doubleFieldDeclarations = { - | Define _: <- toRel(ws.definitions)[currentDefs] + | Define _: <- definitionsRel(ws)[currentDefs] // The scope of a field def is the surrounding data def , loc dataDef <- rascalGetOverloadedDefs(ws, {curFieldScope}, rascalMayOverloadSameName) , loc nD <- (newDefs)[fieldId()] & (ws.defines)[fieldId(), dataDef] @@ -111,9 +111,8 @@ private set[IllegalRenameReason] rascalCheckCausesDoubleDeclarations(WorkspaceIn , cT: aparameter(_, _) := ws.facts[cD] , Define fD: <_, _, _, _, _, defType(afunc(_, funcParams:/cT, _))> <- ws.defines , isContainedIn(cD, fD.defined) - , loc nD <- ws.facts + , <- toRel(ws.facts) , isContainedIn(nD, fD.defined) - , nT: aparameter(newName, _) := ws.facts[nD] , /nT := funcParams }; @@ -232,8 +231,8 @@ private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitio private bool rascalIsFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { for (d <- defs) { - if (Define _: <_, _, _, _, funDef, defType(afunc(_, _, _))> <- ws.defines - , isContainedIn(ws.definitions[d].scope, funDef)) { + if (Define fun: <_, _, _, _, _, defType(afunc(_, _, _))> <- ws.defines + , isContainedIn(ws.definitions[d].scope, fun.defined)) { continue; } return false; @@ -249,8 +248,6 @@ private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = private default bool rascalIsFunctionLocal(_, _) = false; private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, loc lhs) { - rel[loc, Define] definitionsRel = toRel(ws.definitions); - set[loc] fromDefs = (ws.definitions[lhs]? || lhs in ws.useDef<1>) ? {lhs} : getDefs(ws, lhs) @@ -262,12 +259,12 @@ private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, loc lhs) { | loc cD <- rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) , Define cons: <_, _, _, constructorId(), _, _> := ws.definitions[cD] , AType consAdtType := cons.defInfo.atype.adt - , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- definitionsRel[rascalReachableDefs(ws, {cons.defined})] + , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- rascalReachableDefs(ws, {cons.defined}) }; } else if (rascalIsDataType(lhsType)) { return {adt | set[loc] overloads := rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) - , Define adt: <_, _, _, dataId(), _, defType(lhsType)> <- definitionsRel[rascalReachableDefs(ws, overloads)] + , Define adt: <_, _, _, dataId(), _, defType(lhsType)> <- rascalReachableDefs(ws, overloads) }; } @@ -340,13 +337,6 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { throw unsupportedRename("Renaming \'\' at is not supported."); } - // print("Defines: "); - // iprintln(ws.definitions); - - // print("Facts: "); - // iprintln(ws.facts); - - Cursor getDataFieldCursor(loc container) { for (Define dt <- rascalGetADTDefinitions(ws, container) , adtType := dt.defInfo.atype) { @@ -410,9 +400,8 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , Define adt: <_, _, _, dataId(), _, _> <- ws.defines , isStrictlyContainedIn(c, adt.defined)) { return getDataFieldCursor(adt.defined); - } else { - return cursor(def(), c, cursorName); } + return cursor(def(), c, cursorName); } case {use(), *_}: { set[loc] defs = getDefs(ws, c); @@ -429,14 +418,6 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { ) { // Cursor is at a qualified name return cursor(moduleName(), c, cursorName); - // } else if (/Tree t := parseModuleWithSpacesCached(cursorLoc.top) - // , just() := getFieldLoc(cursorName, t) - // , just(acons(adtType, _, _)) := getFact(ws, lhs) - // , Define dataDef: <_, _, _, dataId(), _, defType(AType adtType)> <- ws.defines - // , Define kwDef: <_, cursorName, _, keywordFormalId(), _, _> <- ws.defines - // , isStrictlyContainedIn(kwDef.defined, dataDef.defined)) { - // // Cursor is at a field use - // return cursor(dataField(), kwDef.defined, cursorName); } else if (defines != {}) { // The cursor is at a use with corresponding definitions. return cursor(use(), c, cursorName); @@ -513,11 +494,11 @@ private bool rascalContainsName(loc l, str name) { 3. It does not change definitions outside of the current workspace. } list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, str newName, PathConfig(loc) getPathConfig) - { // }= job("renaming to ", list[DocumentEdit](void(str, int) step) { + = job("renaming to ", list[DocumentEdit](void(str, int) step) { loc cursorLoc = cursorT.src; str cursorName = ""; - // step("collecting workspace information", 1); + step("collecting workspace information", 1); WorkspaceInfo ws = workspaceInfo( // Preload ProjectFiles() { @@ -557,27 +538,18 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s } ); - // step("preloading minimal workspace information", 1); + step("preloading minimal workspace information", 1); ws = preLoad(ws); - // step("analyzing name at cursor", 1); + step("analyzing name at cursor", 1); cur = rascalGetCursor(ws, cursorT); - // step("loading required type information", 1); + step("loading required type information", 1); if (!rascalIsFunctionLocal(ws, cur)) { - // println("Renaming not module-local; loading more information from workspace."); ws = loadWorkspace(ws); - // } else { - // println("Renaming guaranteed to be module-local."); } - // print("Defines: "); - // iprintln({ | d <- ws.defines}["Foo"]); - - // print("Scopes: "); - // iprintln(ws.scopes); - - // step("collecting uses of \'\'", 1); + step("collecting uses of \'\'", 1); = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName, getPathConfig); rel[loc file, loc defines] defsPerFile = { | d <- defs}; @@ -585,7 +557,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s set[loc] \files = defsPerFile.file + usesPerFile.file; - // step("checking rename validity", 1); + step("checking rename validity", 1); map[loc, tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits]] moduleResults = (file: | file <- \files, := computeTextEdits(ws, file, defsPerFile[file], usesPerFile[file], newName)); @@ -598,7 +570,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s list[DocumentEdit] renames = [renamed(from, to) | <- getRenames(newName)]; return changes + renames; -}//, totalWork = 6); +}, totalWork = 6); //// WORKAROUNDS diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index fa506c59a..1c4c12438 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -186,22 +186,26 @@ set[loc] rascalReachableModules(WorkspaceInfo ws, set[loc] froms) { return {s.top | s <- reachable.modScope}; } -set[loc] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { +@memo{maximumSize=1, minutes=5} +rel[loc, Define] definitionsRel(WorkspaceInfo ws) = toRel(ws.definitions); + +set[Define] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); rel[loc from, loc to] scopes = rascalGetTransitiveReflexiveScopes(ws); - rel[loc from, loc to] reachableDefs = + rel[loc from, Define define] reachableDefs = (ws.defines)[defs] o scopes // Find the module scope o modulePaths - o ws.defines; - return reachableDefs.to; + o ws.defines + o definitionsRel(ws); + return reachableDefs.define; } set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { if (defs == {}) return {}; set[loc] overloadedDefs = defs; - set[IdRole] roles = toRel(ws.definitions)[defs].idRole; + set[IdRole] roles = definitionsRel(ws)[defs].idRole; // Pre-conditions assert size(roles) == 1: @@ -282,10 +286,12 @@ set[loc] rascalGetKeywordArgs(\default(_, keywordArgs), str cursorName) = set[loc] rascalGetKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = getUses(ws, defs); + set[Define] reachableDefs = rascalReachableDefs(ws, defs); + for (d <- defs - , Define dataDef: <_, _, _, dataId(), _, _> <- ws.defines + , Define dataDef: <_, _, _, dataId(), _, _> <- reachableDefs , isStrictlyContainedIn(d, dataDef.defined) - , Define consDef: <_, _, _, constructorId(), _, _> <- ws.defines + , Define consDef: <_, _, _, constructorId(), _, _> <- reachableDefs , isStrictlyContainedIn(consDef.defined, dataDef.defined) ) { if (AType fieldType := ws.definitions[d].defInfo.atype) { @@ -366,10 +372,11 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, } else if (cursorLoc in ws.defines) { initialDefs = {cursorLoc}; } else if (just(AType adtType) := getFact(ws, cursorLoc)) { + set[Define] reachableDefs = rascalReachableDefs(ws, {cursorLoc}); initialDefs = { kwDef.defined | Define dataDef: <_, _, _, dataId(), _, defType(adtType)> <- rascalGetADTDefinitions(ws, cursorLoc) - , Define kwDef: <_, _, cursorName, keywordFormalId(), _, _> <- ws.defines + , Define kwDef: <_, _, cursorName, keywordFormalId(), _, _> <- reachableDefs , isStrictlyContainedIn(kwDef.defined, dataDef.defined) }; } else { @@ -392,8 +399,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc set[loc] uses = {}; set[loc] adtDefs = rascalGetOverloadedDefs(ws, {cursorKind.dataTypeDef}, mayOverloadF); - rel[loc, Define] definitionsRel = toRel(ws.definitions); - set[Define] reachableDefs = definitionsRel[rascalReachableDefs(ws, adtDefs)]; + set[Define] reachableDefs = rascalReachableDefs(ws, adtDefs); set[loc] reachableModules = rascalReachableModules(ws, reachableDefs.defined); for (Define _:<_, _, _, constructorId(), _, defType(AType consType)> <- reachableDefs) { @@ -539,7 +545,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorL // moduleNameSize ^ qualSepSize ^ ^ idSize trim(u, removePrefix = moduleNameSize - size(cursorName) , removeSuffix = idSize + qualSepSize) - | <- ws.useDef o toRel(ws.definitions) + | <- ws.useDef o definitionsRel(ws) , idSize := size(d.id) , u.length > idSize // There might be a qualified prefix , moduleNameSize := size(modName) From 72a3202f9fae127765a61b67493f675fb21fe164 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 2 Sep 2024 16:14:52 +0200 Subject: [PATCH 099/105] Fix type errors. --- .../lang/rascal/lsp/refactor/Rename.rsc | 36 +------------------ .../rascal/lsp/refactor/WorkspaceInfo.rsc | 33 +++++++++++++++++ 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index c45a89161..ddfcecdc0 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -220,15 +220,6 @@ private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTex private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, loc moduleLoc, set[loc] defs, set[loc] uses, str name) = computeTextEdits(ws, parseModuleWithSpacesCached(moduleLoc), defs, uses, name); -private bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { - if (l <- defs, !definitions[l]?) return false; - set[str] names = {definitions[l].id | l <- defs}; - if (size(names) > 1) return false; - - map[loc, Define] potentialOverloadDefinitions = (l: d | l <- definitions, d := definitions[l], d.id in names); - return rascalMayOverload(defs, potentialOverloadDefinitions); -} - private bool rascalIsFunctionLocalDefs(WorkspaceInfo ws, set[loc] defs) { for (d <- defs) { if (Define fun: <_, _, _, _, _, defType(afunc(_, _, _))> <- ws.defines @@ -247,30 +238,6 @@ private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _) private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true; private default bool rascalIsFunctionLocal(_, _) = false; -private set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, loc lhs) { - set[loc] fromDefs = (ws.definitions[lhs]? || lhs in ws.useDef<1>) - ? {lhs} - : getDefs(ws, lhs) - ; - - AType lhsType = ws.facts[lhs]; - if (rascalIsConstructorType(lhsType)) { - return {adt - | loc cD <- rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) - , Define cons: <_, _, _, constructorId(), _, _> := ws.definitions[cD] - , AType consAdtType := cons.defInfo.atype.adt - , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- rascalReachableDefs(ws, {cons.defined}) - }; - } else if (rascalIsDataType(lhsType)) { - return {adt - | set[loc] overloads := rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) - , Define adt: <_, _, _, dataId(), _, defType(lhsType)> <- rascalReachableDefs(ws, overloads) - }; - } - - return {}; -} - Maybe[AType] rascalAdtCommonKeywordFieldType(WorkspaceInfo ws, str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) { if (defInfo.commonKeywordFields? , kwf:(KeywordFormal) ` = ` <- defInfo.commonKeywordFields @@ -388,9 +355,8 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { || maybeContainerType == nothing()) { // Case 1 (or 0): collection field return cursor(collectionField(), c, cursorName); - } else { - return getDataFieldCursor(container); } + return getDataFieldCursor(container); } } case {def(), *_}: { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 1c4c12438..8abf768d8 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -250,6 +250,39 @@ bool rascalIsCollectionType(AType at) = at is arel || at is alrel || at is atupl bool rascalIsConstructorType(AType at) = at is acons; bool rascalIsDataType(AType at) = at is aadt; +bool rascalMayOverloadSameName(set[loc] defs, map[loc, Define] definitions) { + if (l <- defs, !definitions[l]?) return false; + set[str] names = {definitions[l].id | l <- defs}; + if (size(names) > 1) return false; + + map[loc, Define] potentialOverloadDefinitions = (l: d | l <- definitions, d := definitions[l], d.id in names); + return rascalMayOverload(defs, potentialOverloadDefinitions); +} + +set[Define] rascalGetADTDefinitions(WorkspaceInfo ws, loc lhs) { + set[loc] fromDefs = (ws.definitions[lhs]? || lhs in ws.useDef<1>) + ? {lhs} + : getDefs(ws, lhs) + ; + + AType lhsType = ws.facts[lhs]; + if (rascalIsConstructorType(lhsType)) { + return {adt + | loc cD <- rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) + , Define cons: <_, _, _, constructorId(), _, _> := ws.definitions[cD] + , AType consAdtType := cons.defInfo.atype.adt + , Define adt: <_, _, _, dataId(), _, defType(consAdtType)> <- rascalReachableDefs(ws, {cons.defined}) + }; + } else if (rascalIsDataType(lhsType)) { + return {adt + | set[loc] overloads := rascalGetOverloadedDefs(ws, fromDefs, rascalMayOverloadSameName) + , Define adt: <_, _, _, dataId(), _, defType(lhsType)> <- rascalReachableDefs(ws, overloads) + }; + } + + return {}; +} + set[loc] rascalGetKeywordFormalUses(WorkspaceInfo ws, set[loc] defs, str cursorName) { set[loc] uses = {}; From 7dd59d09bb14c3d5d9b01a117556004bcb806c57 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 2 Sep 2024 16:16:08 +0200 Subject: [PATCH 100/105] Accept renaming from collection field projection. --- .../main/rascal/lang/rascal/lsp/refactor/Util.rsc | 2 ++ .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 14 ++++++++++---- .../rascal/lang/rascal/tests/rename/Fields.rsc | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index bb5689657..a71e25de8 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -101,6 +101,8 @@ rel[&K, &V] groupBy(set[&V] s, &K(&V) pred) = } bool byLength(loc l1, loc l2) = l1.length < l2.length; +bool byLengthTuple(tuple[loc, &T] t1, tuple[loc, &T] t2) = byLength(t1[0], t2[0]); + @synopsis{ Predicate to sort locations by offset. } diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 8abf768d8..0df2e8a24 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -456,12 +456,18 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { bool isTupleField(AType fieldType) = fieldType.alabel == ""; - AType cursorType = ws.facts[cursorLoc]; - list[loc] factLocsSortedBySize = sort(domain(ws.facts), byLength); + lrel[loc, AType] factsBySize = sort(toRel(ws.facts), byLengthTuple); + AType cursorType = avoid(); + + if (ws.facts[cursorLoc]?) { + cursorType = ws.facts[cursorLoc]; + } else if (just(loc fieldAccess) := findSmallestContaining(ws.facts<0>, cursorLoc) + , just(AType collectionType) := getFact(ws, fieldAccess)) { + cursorType = collectionType; + } - if (l <- factLocsSortedBySize + if ( <- factsBySize , isStrictlyContainedIn(cursorLoc, l) - , collUseType := ws.facts[l] , rascalIsCollectionType(collUseType) , collUseType.elemType is atypeList) { // We are at a collection definition site diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc index fe28bcbf3..0b3b6aaca 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc @@ -181,13 +181,13 @@ test bool lrelField() = testRenameOccurrences({0, 1}, " test bool relSubscript() = testRenameOccurrences({0, 1}, " 'rel[str foo, str baz] r = {}; 'x = r\; -", skipCursors = {1}); +"); test bool relSubscriptWithVar() = testRenameOccurrences({0, 2}, " 'rel[str foo, str baz] r = {}; 'str foo = \"foo\"; 'x = r\; -", skipCursors = {2}); +"); test bool tupleFieldSubscriptUpdate() = testRenameOccurrences({0, 1, 2}, " 'tuple[str foo, int baz] t = \<\"one\", 1\>; From 0a2ac13f04394eb84aabc4a0ea25b8bbafb790e3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 2 Sep 2024 17:38:06 +0200 Subject: [PATCH 101/105] Update documentation. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index ddfcecdc0..5e9ace4b0 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -431,11 +431,11 @@ private bool rascalContainsName(loc l, str name) { - Aliases - Data types - Type parameters + - Data constructors + - Data constructor fields (fields, keyword fields, common keyword fields) The following symbols are currently unsupported. - Annotations (on functions) - - Data constructors - - Data constructor fields (fields, keyword fields, common keyword fields) *Name resolution* A renaming triggers the typechecker on the currently open file to determine the scope of the renaming. From 6b6c336d23d3b4e88638da4450171cb903c711d8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 2 Sep 2024 17:51:54 +0200 Subject: [PATCH 102/105] Clean up some more debugging artefacts. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 2 -- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 4 ---- 2 files changed, 6 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 5e9ace4b0..47f6ed201 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -328,8 +328,6 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { } loc c = min(locsContainingCursor.l); - // print("Locs containing cursor: "); - // iprintln(locsContainingCursor); switch (locsContainingCursor[c]) { case {moduleName(), *_}: { return cursor(moduleName(), c, cursorName); diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 0df2e8a24..961bc722d 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -47,8 +47,6 @@ import Map; import Set; import String; -import IO; - data CursorKind = use() | def() @@ -424,8 +422,6 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, bool debug = false; -&T printlnExpD(str msg, &T t) = debug ? printlnExp(msg, t) : t; - DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { if (cursorKind is dataKeywordField || cursorKind is dataCommonKeywordField) { set[loc] defs = {}; From fac01e36f1007424254d596e8916d7cc22333813 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 5 Sep 2024 11:04:44 +0200 Subject: [PATCH 103/105] Small fixes based on review by @PieterOlivier --- .../rascal/lang/rascal/lsp/refactor/Util.rsc | 4 ++-- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 16 ++++++++-------- .../lang/rascal/tests/rename/TestUtils.rsc | 6 ------ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc index a71e25de8..d062a5df2 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Util.rsc @@ -99,9 +99,9 @@ rel[&K, &V] groupBy(set[&V] s, &K(&V) pred) = @synopsis{ Predicate to sort locations by length. } -bool byLength(loc l1, loc l2) = l1.length < l2.length; +bool isShorter(loc l1, loc l2) = l1.length < l2.length; -bool byLengthTuple(tuple[loc, &T] t1, tuple[loc, &T] t2) = byLength(t1[0], t2[0]); +bool isShorterTuple(tuple[loc, &T] t1, tuple[loc, &T] t2) = isShorter(t1[0], t2[0]); @synopsis{ Predicate to sort locations by offset. diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 961bc722d..95d7a0f5f 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -160,7 +160,7 @@ rel[loc from, loc to] rascalGetTransitiveReflexiveModulePaths(WorkspaceInfo ws) rel[loc from, loc to] imports = (ws.paths)[importPath()]; rel[loc from, loc to] extends = (ws.paths)[extendPath()]; - return (moduleI + imports) // o or 1 imports + return (moduleI + imports) // 0 or 1 imports o (moduleI + extends+) // 0 or more extends ; } @@ -191,12 +191,12 @@ set[Define] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { rel[loc from, loc to] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); rel[loc from, loc to] scopes = rascalGetTransitiveReflexiveScopes(ws); rel[loc from, Define define] reachableDefs = - (ws.defines)[defs] - o scopes // Find the module scope - o modulePaths - o ws.defines - o definitionsRel(ws); - return reachableDefs.define; + (ws.defines)[defs] // pairs + o scopes // All scopes surrounding defs + o modulePaths // Transitive-reflexive paths from scope to reachable modules + o ws.defines // Definitions in these scopes + o definitionsRel(ws); // Full define tuples + return reachableDefs.define; // We are only interested in reached defines; not *from where* they were reached } set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { @@ -452,7 +452,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { bool isTupleField(AType fieldType) = fieldType.alabel == ""; - lrel[loc, AType] factsBySize = sort(toRel(ws.facts), byLengthTuple); + lrel[loc, AType] factsBySize = sort(toRel(ws.facts), isShorterTuple); AType cursorType = avoid(); if (ws.facts[cursorLoc]?) { diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc index a3fdfa0fa..d28a5e5d6 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc @@ -66,10 +66,6 @@ private void verifyTypeCorrectRenaming(loc root, list[DocumentEdit] edits, PathC executeDocumentEdits(sortEdits(edits)); remove(pcfg.resources); RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true][verbose = false][logPathConfig = false]; - // for (f <- find(root, "rsc")) { - // println(f); - // println(parseModuleWithSpaces(f)); - // } throwAnyErrors(checkAll(root, ccfg)); } @@ -241,8 +237,6 @@ tuple[list[DocumentEdit], set[int]] getEditsAndOccurrences(loc singleModule, loc edits = getEdits(singleModule, {projectDir}, cursorAtOldNameOccurrence, oldName, newName, PathConfig(loc _) { return pcfg; }); occs = extractRenameOccurrences(singleModule, edits, oldName); - // println("Occurrences: "); - for (src <- pcfg.srcs) { verifyTypeCorrectRenaming(src, edits, pcfg); } From 9c8c98ff1e25ea449eb5f8a2cb8bb3b82628aad7 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 5 Sep 2024 11:10:51 +0200 Subject: [PATCH 104/105] Move path config function to workspace info field h/t @PieterOlivier --- .../rascal/lang/rascal/lsp/refactor/Rename.rsc | 4 +++- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 17 +++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 47f6ed201..5b538f80e 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -464,6 +464,8 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s step("collecting workspace information", 1); WorkspaceInfo ws = workspaceInfo( + // Get path config + getPathConfig, // Preload ProjectFiles() { return { < @@ -514,7 +516,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s } step("collecting uses of \'\'", 1); - = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName, getPathConfig); + = rascalGetDefsUses(ws, cur, rascalMayOverloadSameName); rel[loc file, loc defines] defsPerFile = { | d <- defs}; rel[loc file, loc uses] usesPerFile = { | u <- uses}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc index 95d7a0f5f..9c1d79292 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc @@ -84,6 +84,7 @@ data WorkspaceInfo ( Paths paths = {}, set[loc] projects = {} ) = workspaceInfo( + PathConfig(loc) getPathConfig, ProjectFiles() preloadFiles, ProjectFiles() allFiles, set[TModel](ProjectFiles) tmodelsForLocs @@ -338,7 +339,7 @@ set[loc] rascalGetKeywordFieldUses(WorkspaceInfo ws, set[loc] defs, str cursorNa return uses; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName), MayOverloadFun mayOverloadF) { defs = rascalGetOverloadedDefs(ws, getDefs(ws, l), mayOverloadF); uses = getUses(ws, defs); @@ -350,7 +351,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(use(), l, cursorName) return ; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName), MayOverloadFun mayOverloadF) { set[loc] initialUses = getUses(ws, l); set[loc] initialDefs = {l} + {*ds | u <- initialUses, ds := getDefs(ws, u)}; defs = rascalGetOverloadedDefs(ws, initialDefs, mayOverloadF); @@ -364,7 +365,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(def(), l, cursorName) return ; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLoc, cursorName), MayOverloadFun _) { AType at = ws.facts[cursorLoc]; set[loc] defs = {}; set[loc] useDefs = {}; @@ -396,7 +397,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLo return ; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, AType fieldType), cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, AType fieldType), cursorLoc, cursorName), MayOverloadFun mayOverloadF) { set[loc] initialDefs = {}; if (cursorLoc in ws.useDef<0>) { initialDefs = getDefs(ws, cursorLoc); @@ -422,7 +423,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(dataField(loc adtLoc, bool debug = false; -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc, cursorName), MayOverloadFun mayOverloadF, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc, cursorName), MayOverloadFun mayOverloadF) { if (cursorKind is dataKeywordField || cursorKind is dataCommonKeywordField) { set[loc] defs = {}; set[loc] uses = {}; @@ -449,7 +450,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(cursorKind, cursorLoc fail; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) _) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(collectionField(), cursorLoc, cursorName), MayOverloadFun _) { bool isTupleField(AType fieldType) = fieldType.alabel == ""; lrel[loc, AType] factsBySize = sort(toRel(ws.facts), isShorterTuple); @@ -551,7 +552,7 @@ private DefsUsesRenames rascalGetFieldDefsUses(WorkspaceInfo ws, set[loc] reacha return ; } -DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _, PathConfig(loc) getPathConfig) { +DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorLoc, cursorName), MayOverloadFun _) { loc moduleFile = |unknown:///|; if (d <- ws.useDef[cursorLoc], amodule(_) := ws.facts[d]) { // Cursor is at an import @@ -568,7 +569,7 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(moduleName(), cursorL } } - modName = getModuleName(moduleFile, getPathConfig(getProjectFolder(ws, moduleFile))); + modName = getModuleName(moduleFile, ws.getPathConfig(getProjectFolder(ws, moduleFile))); defs = {parseModuleWithSpacesCached(moduleFile).top.header.name.names[-1].src}; From 54d1a4141a588d5bebddd577ea9e51cb90742c9e Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 5 Sep 2024 13:30:13 +0200 Subject: [PATCH 105/105] Split up logic to determine cursor kind. --- .../lang/rascal/lsp/refactor/Rename.rsc | 155 +++++++++--------- 1 file changed, 80 insertions(+), 75 deletions(-) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc index 5b538f80e..4508c5ee9 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc @@ -258,83 +258,38 @@ Maybe[AType] rascalConsFieldType(str fieldName, Define _:<_, _, _, constructorId return nothing(); } -Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { - loc cursorLoc = cursorT.src; - str cursorName = ""; - - rel[loc field, loc container] fields = { - | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) - , just() := rascalGetFieldLocs(cursorName, t) - , loc fieldLoc <- fieldLocs - }; - - rel[loc kw, loc container] keywords = { - | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) - , just() := rascalGetKeywordLocs(cursorName, t) - , loc kwLoc <- kwLocs - }; - - Maybe[loc] smallestFieldContainingCursor = findSmallestContaining(fields.field, cursorLoc); - Maybe[loc] smallestKeywordContainingCursor = findSmallestContaining(keywords.kw, cursorLoc); - - rel[loc l, CursorKind kind] locsContainingCursor = { - - | <- { - // Uses - , cursorLoc), use()> - // Defs with an identifier equals the name under the cursor - , )[cursorName], cursorLoc), def()> - // Type parameters - , - // Any kind of field; we'll decide which exactly later - , - , - , - , - // Any kind of keyword param; we'll decide which exactly later - , - , - , - // Module name declaration, where the cursor location is in the module header - , - } - }; - - if (locsContainingCursor == {}) { - throw unsupportedRename("Renaming \'\' at is not supported."); - } - - Cursor getDataFieldCursor(loc container) { - for (Define dt <- rascalGetADTDefinitions(ws, container) - , adtType := dt.defInfo.atype) { - if (just(fieldType) := rascalAdtCommonKeywordFieldType(ws, cursorName, dt)) { - // Case 4 or 5 (or 0): common keyword field - return cursor(dataCommonKeywordField(dt.defined, fieldType), c, cursorName); - } +private CursorKind rascalGetDataFieldCursorKind(WorkspaceInfo ws, loc container, loc cursorLoc, str cursorName) { + for (Define dt <- rascalGetADTDefinitions(ws, container) + , adtType := dt.defInfo.atype) { + if (just(fieldType) := rascalAdtCommonKeywordFieldType(ws, cursorName, dt)) { + // Case 4 or 5 (or 0): common keyword field + return dataCommonKeywordField(dt.defined, fieldType); + } - for (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { - if (just(fieldType) := rascalConsKeywordFieldType(cursorName, d)) { - // Case 3 (or 0): keyword field - return cursor(dataKeywordField(dt.defined, fieldType), c, cursorName); - } else if (just(fieldType) := rascalConsFieldType(cursorName, d)) { - // Case 2 (or 0): positional field - return cursor(dataField(dt.defined, fieldType), c, cursorName); - } + for (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) { + if (just(fieldType) := rascalConsKeywordFieldType(cursorName, d)) { + // Case 3 (or 0): keyword field + return dataKeywordField(dt.defined, fieldType); + } else if (just(fieldType) := rascalConsFieldType(cursorName, d)) { + // Case 2 (or 0): positional field + return dataField(dt.defined, fieldType); } } - - set[loc] fromDefs = cursorLoc in ws.useDef<1> ? {cursorLoc} : getDefs(ws, cursorLoc); - throw illegalRename("Cannot rename \'\'; it is not defined in this workspace", {definitionsOutsideWorkspace(fromDefs)}); } + set[loc] fromDefs = cursorLoc in ws.useDef<1> ? {cursorLoc} : getDefs(ws, cursorLoc); + throw illegalRename("Cannot rename \'\'; it is not defined in this workspace", {definitionsOutsideWorkspace(fromDefs)}); +} + +private CursorKind rascalGetCursorKind(WorkspaceInfo ws, loc cursorLoc, str cursorName, rel[loc l, CursorKind kind] locsContainingCursor, rel[loc field, loc container] fields, rel[loc kw, loc container] keywords) { loc c = min(locsContainingCursor.l); switch (locsContainingCursor[c]) { case {moduleName(), *_}: { - return cursor(moduleName(), c, cursorName); + return moduleName(); } case {keywordParam(), dataKeywordField(_, _), *_}: { if ({loc container} := keywords[c]) { - return getDataFieldCursor(container); + return rascalGetDataFieldCursorKind(ws, container, cursorLoc, cursorName); } } case {collectionField(), dataField(_, _), dataKeywordField(_, _), dataCommonKeywordField(_, _), *_}: { @@ -352,9 +307,9 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if ((just(containerType) := maybeContainerType && rascalIsCollectionType(containerType)) || maybeContainerType == nothing()) { // Case 1 (or 0): collection field - return cursor(collectionField(), c, cursorName); + return collectionField(); } - return getDataFieldCursor(container); + return rascalGetDataFieldCursorKind(ws, container, cursorLoc, cursorName); } } case {def(), *_}: { @@ -363,9 +318,9 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (d.idRole is fieldId , Define adt: <_, _, _, dataId(), _, _> <- ws.defines , isStrictlyContainedIn(c, adt.defined)) { - return getDataFieldCursor(adt.defined); + return rascalGetDataFieldCursorKind(ws, adt.defined, cursorLoc, cursorName); } - return cursor(def(), c, cursorName); + return def(); } case {use(), *_}: { set[loc] defs = getDefs(ws, c); @@ -373,7 +328,7 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { if (d <- defs, just(amodule(_)) := getFact(ws, d)) { // Cursor is at an import - return cursor(moduleName(), c, cursorName); + return moduleName(); } else if (u <- ws.useDef<0> , isContainedIn(cursorLoc, u) , u.end > cursorLoc.end @@ -381,24 +336,74 @@ Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { , {variableId()} !:= (ws.defines)[getDefs(ws, u)] ) { // Cursor is at a qualified name - return cursor(moduleName(), c, cursorName); + return moduleName(); } else if (defines != {}) { // The cursor is at a use with corresponding definitions. - return cursor(use(), c, cursorName); + return use(); } else if (just(at) := getFact(ws, c) , aparameter(cursorName, _) := at) { // The cursor is at a type parameter - return cursor(typeParam(), c, cursorName); + return typeParam(); } } case {k}: { - return cursor(k, c, cursorName); + return k; } } throw unsupportedRename("Could not retrieve information for \'\' at ."); } +private Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) { + loc cursorLoc = cursorT.src; + str cursorName = ""; + + rel[loc field, loc container] fields = { + | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) + , just() := rascalGetFieldLocs(cursorName, t) + , loc fieldLoc <- fieldLocs + }; + + rel[loc kw, loc container] keywords = { + | /Tree t := parseModuleWithSpacesCached(cursorLoc.top) + , just() := rascalGetKeywordLocs(cursorName, t) + , loc kwLoc <- kwLocs + }; + + Maybe[loc] smallestFieldContainingCursor = findSmallestContaining(fields.field, cursorLoc); + Maybe[loc] smallestKeywordContainingCursor = findSmallestContaining(keywords.kw, cursorLoc); + + rel[loc l, CursorKind kind] locsContainingCursor = { + + | <- { + // Uses + , cursorLoc), use()> + // Defs with an identifier equals the name under the cursor + , )[cursorName], cursorLoc), def()> + // Type parameters + , + // Any kind of field; we'll decide which exactly later + , + , + , + , + // Any kind of keyword param; we'll decide which exactly later + , + , + , + // Module name declaration, where the cursor location is in the module header + , + } + }; + + if (locsContainingCursor == {}) { + throw unsupportedRename("Renaming \'\' at is not supported."); + } + + CursorKind kind = rascalGetCursorKind(ws, cursorLoc, cursorName, locsContainingCursor, fields, keywords); + return cursor(kind, min(locsContainingCursor.l), cursorName); +} + private set[Name] rascalNameToEquivalentNames(str name) = { [Name] name, startsWith(name, "\\") ? [Name] name : [Name] "\\"