From 5223c284d434e8154b62109b085362e5019d9c94 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 18 Oct 2024 11:46:12 +0200 Subject: [PATCH 01/14] Refine resolution of overloaded field declarations Field with identical name/type signatures from distinct ADTs would incorrectly be classified as mutual overloads. This commit adds tests for this and refines the overload detection to solve this. --- .../lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 10 +++++++++- .../main/rascal/lang/rascal/tests/rename/Fields.rsc | 13 +++++++++++++ 2 files changed, 22 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 443dd28f6..17430d448 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 @@ -237,12 +237,20 @@ set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun o scopeDefs // 3. Find definitions in the reached scope, and definitions within those definitions (transitively) ; - rel[loc from, loc to] defPaths = {}; + rel[loc from, loc to] defPaths = fromDefPaths; if (constructorId() := role) { // We are just looking for constructors for the same ADT/nonterminal type rel[loc, loc] selectedConstructors = (ws.defines)[originalDefs.defInfo]; defPaths = (defPaths o selectedConstructors) + (invert(defPaths) o selectedConstructors); + } else if (fieldId() := role) { + // We are looking for fields for the same ADT type (but not necessarily same constructor type) + set[DefInfo] selectedADTTypes = (ws.defines)[originalDefs.scope]; + rel[loc, loc] selectedADTs = (ws.defines)[selectedADTTypes]; + rel[loc, loc] selectedFields = selectedADTs o ws.defines; + + defPaths = (defPaths o selectedFields) + + invert(defPaths) o selectedFields; } else { defPaths = fromDefPaths + invert(fromDefPaths); } 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 794dc2097..781561be7 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 @@ -81,6 +81,19 @@ test bool commonKeywordFieldsSameType() = testRenameOccurrences({0, 1}, decls = "data D (set[loc] foo = {}, set[loc] baz = {})= d();" ); +test bool sameNameFields() = testRenameOccurrences({0, 2}, " + 'D x = d(8); + 'int i = x.foo; +", decls = " + 'data D = d(int foo); + 'data E = e(int foo); +"); + +test bool sameNameFieldsDisconnectedModules() = testRename({ + byText("A", "data D = d(int foo);", {0}) + , byText("B", "data E = e(int foo);") +}); + test bool complexDataType() = testRenameOccurrences({0, 1}, "WorkspaceInfo ws = workspaceInfo( ' ProjectFiles() { return {}; }, From d4650501a89d37d99098ba6ff3532b4c91c299cb Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 22 Oct 2024 11:10:41 +0200 Subject: [PATCH 02/14] Remove outdated comment. --- rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Fields.rsc | 2 -- 1 file changed, 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 781561be7..0330677a5 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,8 +47,6 @@ 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; From bb81531e15c65867e4d57cc1582e1f1435bc3894 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 22 Oct 2024 11:11:21 +0200 Subject: [PATCH 03/14] Fix test function calls. --- .../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 0330677a5..0aa19e462 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 @@ -87,9 +87,9 @@ test bool sameNameFields() = testRenameOccurrences({0, 2}, " 'data E = e(int foo); "); -test bool sameNameFieldsDisconnectedModules() = testRename({ +test bool sameNameFieldsDisconnectedModules() = testRenameOccurrences({ byText("A", "data D = d(int foo);", {0}) - , byText("B", "data E = e(int foo);") + , byText("B", "data E = e(int foo);", {}) }); test bool complexDataType() = testRenameOccurrences({0, 1}, From 6fcec6062820e4f56df32a44ea91e803e6111297 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 22 Oct 2024 13:11:52 +0200 Subject: [PATCH 04/14] Rewrite and fix overload resolution. --- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 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 17430d448..ec4d8cc53 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 @@ -226,33 +226,26 @@ 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)+ // 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 - 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; + + rel[loc from, loc to] defPaths = + (defScopes + defUseScopes) // 1. Look up scopes of defs and scopes of their uses + o (modulePaths + invert(modulePaths)) // 2. Follow import/extend relations to reachable scopes + ; + if (constructorId() := role) { // We are just looking for constructors for the same ADT/nonterminal type rel[loc, loc] selectedConstructors = (ws.defines)[originalDefs.defInfo]; - defPaths = (defPaths o selectedConstructors) - + (invert(defPaths) o selectedConstructors); + defPaths = defPaths o selectedConstructors; } else if (fieldId() := role) { // We are looking for fields for the same ADT type (but not necessarily same constructor type) set[DefInfo] selectedADTTypes = (ws.defines)[originalDefs.scope]; - rel[loc, loc] selectedADTs = (ws.defines)[selectedADTTypes]; + rel[loc, loc] selectedADTs = (ws.defines)[selectedADTTypes]; rel[loc, loc] selectedFields = selectedADTs o ws.defines; - - defPaths = (defPaths o selectedFields) - + invert(defPaths) o selectedFields; + defPaths = defPaths o selectedFields; } else { - defPaths = fromDefPaths + invert(fromDefPaths); + // Find definitions in the reached scope, and definitions within those definitions (transitively) + rel[loc scope, loc def] allDefs = (ws.defines)+; + defPaths = defPaths o allDefs; } solve(overloadedDefs) { From c59a87c128f9a8403db0b60766e4a9101fee1b8d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 22 Oct 2024 13:12:14 +0200 Subject: [PATCH 05/14] Remove unused imports. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 7 ------- 1 file changed, 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 9f6a95de2..2cb75070d 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 @@ -48,11 +48,6 @@ 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; extend lang::rascal::lsp::refactor::Exception; import lang::rascal::lsp::refactor::Util; @@ -60,8 +55,6 @@ import lang::rascal::lsp::refactor::WorkspaceInfo; import analysis::diff::edits::TextEdits; -import vis::Text; - import util::FileSystem; import util::Maybe; import util::Monitor; From 8bdc351663071b9810f42d89a43125e7b2ff1d8b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 22 Oct 2024 13:12:49 +0200 Subject: [PATCH 06/14] Fix type errors. --- .../src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc | 2 +- .../main/rascal/lang/rascal/lsp/refactor/WorkspaceInfo.rsc | 4 ++-- 2 files changed, 3 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 2cb75070d..e50b9f164 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 @@ -279,7 +279,7 @@ Maybe[AType] rascalConsFieldType(str fieldName, Define _:<_, _, _, constructorId private CursorKind rascalGetDataFieldCursorKind(WorkspaceInfo ws, loc container, loc cursorLoc, str cursorName) { for (Define dt <- rascalGetADTDefinitions(ws, container) - , adtType := dt.defInfo.atype) { + , AType 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); 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 ec4d8cc53..89aba266d 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 @@ -453,9 +453,9 @@ DefsUsesRenames rascalGetDefsUses(WorkspaceInfo ws, cursor(typeParam(), cursorLo }); } - bool definesTypeParam(Define _: <_, _, _, functionId(), _, defType(dT)>, AType paramType) = + bool definesTypeParam(Define _: <_, _, _, functionId(), _, defType(AType dT)>, AType paramType) = afunc(_, /paramType, _) := dT; - bool definesTypeParam(Define _: <_, _, _, nonterminalId(), _, defType(dT)>, AType paramType) = + bool definesTypeParam(Define _: <_, _, _, nonterminalId(), _, defType(AType dT)>, AType paramType) = aadt(_, /paramType, _) := dT; default bool definesTypeParam(Define _, AType _) = false; From ea1e2fb98bafdde52c21ff46d3d1fbc3634f1817 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 13:22:26 +0200 Subject: [PATCH 07/14] Add test for unrelated fields with identical names. --- .../src/main/rascal/lang/rascal/tests/rename/Fields.rsc | 5 +++++ 1 file changed, 5 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 0aa19e462..9cbe05dce 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 @@ -87,6 +87,11 @@ test bool sameNameFields() = testRenameOccurrences({0, 2}, " 'data E = e(int foo); "); +test bool sameNameADTFields() = testRenameOccurrences({ + byText("Definer", "data D = d(int foo);", {0}) + , byText("Unrelated", "data D = d(int foo);", {}) +}); + test bool sameNameFieldsDisconnectedModules() = testRenameOccurrences({ byText("A", "data D = d(int foo);", {0}) , byText("B", "data E = e(int foo);", {}) From b309e3655210425dfddd69ae43612581f5b46f21 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 13:22:57 +0200 Subject: [PATCH 08/14] USe deterministic test names. --- .../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 eb75f0147..144319252 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 @@ -89,7 +89,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 - mm.skipCursors)) { - str testName = "Test"; + str testName = "Test__"; loc testDir = |memory://tests/rename/|; if(any(m <- modules, m is byLoc)) { @@ -248,7 +248,7 @@ list[DocumentEdit] getEdits(str stmtsStr, int cursorAtOldNameOccurrence, str old return edits; } -private tuple[list[DocumentEdit], set[int]] 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 ' From bbe3bddb882d270fc2868a3cfca712aeaa4dbd54 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 13:23:32 +0200 Subject: [PATCH 09/14] Check tests for parse errors before attempting rename. --- .../main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 9 +++++++++ 1 file changed, 9 insertions(+) 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 144319252..1b4cb5a6c 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 @@ -101,6 +101,15 @@ 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, skipCursors = m.skipCursors))}; + + for (byLoc(loc ml, _) <- modulesByLocation) { + try { + parseModuleWithSpaces(ml); + } catch _: { + throw "Parse error in test module "; + } + } + cursorT = findCursor([m.file | m <- modulesByLocation, getModuleName(m.file, pcfg) == mm.name][0], oldName, cursorOcc); println("Renaming \'\' from "); From e10a0ddbda4f768177de90cd4ed7cdbbbc691930 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 13:24:34 +0200 Subject: [PATCH 10/14] Skip rename type correctness check in failed tests. --- .../src/main/rascal/lang/rascal/tests/rename/TestUtils.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1b4cb5a6c..b81f1add8 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 @@ -145,7 +145,7 @@ bool testRenameOccurrences(set[TestModule] modules, str oldName = "foo", str new success = false; } - for (src <- pcfg.srcs) { + for (success, src <- pcfg.srcs) { verifyTypeCorrectRenaming(src, edits, pcfg); } From 846973555439576dfb69443cf7591d4cae3118d2 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 13:26:24 +0200 Subject: [PATCH 11/14] Look for overloads until reachable defs are exhausted. --- .../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 89aba266d..bf307a53b 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 @@ -248,13 +248,13 @@ set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun defPaths = defPaths o allDefs; } - solve(overloadedDefs) { - rel[loc from, loc to] reachableDefs = ident(overloadedDefs) o defPaths; - + set[loc] reachableDefs = defPaths[overloadedDefs]; + solve(overloadedDefs, reachableDefs) { overloadedDefs += {d - | loc d <- reachableDefs<1> + | loc d <- reachableDefs , mayOverloadF(overloadedDefs + d, ws.definitions) }; + reachableDefs = defPaths[overloadedDefs]; } return overloadedDefs; From 71673c0d15f165a0983c920e409a4578c125d8e6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 17:08:02 +0200 Subject: [PATCH 12/14] Also resolve defs reachable from scopes. --- .../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 bf307a53b..f7c147d14 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,7 +193,8 @@ 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] // pairs + ((ws.defines)[defs] // pairs + + (ws.defines)[defs]) o ( (scopes // All scopes surrounding defs o modulePaths // Transitive-reflexive paths from scope to reachable modules From 747028292be8beb8398768930f903d1a7f4ead5c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 25 Oct 2024 17:12:08 +0200 Subject: [PATCH 13/14] Implement better constructor & ADT overload resolution. --- .../rascal/lsp/refactor/WorkspaceInfo.rsc | 98 +++++-- .../lang/rascal/tests/rename/Constructors.rsc | 247 ++++++++++++++++++ 2 files changed, 317 insertions(+), 28 deletions(-) create mode 100644 rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc 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 f7c147d14..ef29ebd6f 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 @@ -211,54 +211,96 @@ set[Define] rascalReachableDefs(WorkspaceInfo ws, set[loc] defs) { set[loc] rascalGetOverloadedDefs(WorkspaceInfo ws, set[loc] defs, MayOverloadFun mayOverloadF) { if (defs == {}) return {}; - set[loc] overloadedDefs = defs; - set[Define] originalDefs = definitionsRel(ws)[defs]; - set[IdRole] roles = originalDefs.idRole; + set[Define] overloadedDefs = definitionsRel(ws)[defs]; + set[IdRole] roles = overloadedDefs.idRole; // Pre-conditions assert size(roles) == 1: "Initial defs are of different roles!"; - assert mayOverloadF(overloadedDefs, ws.definitions): + assert mayOverloadF(defs, 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 fromScope, loc toScope] modulePaths = rascalGetTransitiveReflexiveModulePaths(ws); rel[loc def, loc scope] defScopes = ws.defines+; - rel[loc from, loc to] defPaths = + rel[loc def, loc moduleScope] defPathStep = (defScopes + defUseScopes) // 1. Look up scopes of defs and scopes of their uses o (modulePaths + invert(modulePaths)) // 2. Follow import/extend relations to reachable scopes ; - if (constructorId() := role) { - // We are just looking for constructors for the same ADT/nonterminal type - rel[loc, loc] selectedConstructors = (ws.defines)[originalDefs.defInfo]; - defPaths = defPaths o selectedConstructors; - } else if (fieldId() := role) { - // We are looking for fields for the same ADT type (but not necessarily same constructor type) - set[DefInfo] selectedADTTypes = (ws.defines)[originalDefs.scope]; - rel[loc, loc] selectedADTs = (ws.defines)[selectedADTTypes]; - rel[loc, loc] selectedFields = selectedADTs o ws.defines; - defPaths = defPaths o selectedFields; - } else { - // Find definitions in the reached scope, and definitions within those definitions (transitively) - rel[loc scope, loc def] allDefs = (ws.defines)+; - defPaths = defPaths o allDefs; - } + rel[loc fromDef, loc toDef] defPaths = {}; + set[loc] reachableDefs = rascalReachableDefs(ws, overloadedDefs.defined).defined; + + solve(overloadedDefs) { + if (constructorId() := role) { + set[AType] adtTypes = {adtType | defType(acons(AType adtType, _, _)) <- overloadedDefs.defInfo}; + set[loc] initialADTs = { + adtDef + | Define _: <_, _, _, dataId(), loc adtDef, defType(AType adtType)> <- rascalReachableDefs(ws, overloadedDefs.defined) + , adtType in adtTypes + }; + set[loc] selectedADTs = rascalGetOverloadedDefs(ws, initialADTs, mayOverloadF); + + // Any constructor definition of the right type where any `selectedADTs` element is in the reachable defs + rel[loc scope, loc def] selectedConstructors = { + | <- (ws.defines)[role] + , adtType in adtTypes + , any(<_, _, _, dataId(), loc r, _> <- rascalReachableDefs(ws, {d}), r in selectedADTs) + }; + + // We transitively resolve module scopes via modules that have a relevant constructor/ADTs use or definition + rel[loc scope, loc def] selectedDefs = selectedConstructors + (ws.defines)[selectedADTs]; + rel[loc fromScope, loc toScope] constructorStep = (selectedDefs + invert(defUseScopes)) o defPathStep; + + defPathStep = defPathStep /* */ + + (defPathStep /* */ o constructorStep+ /* */) /* */; + + defPaths = defPathStep /* */ o selectedConstructors /* */; + } else if (dataId() := role) { + set[AType] adtTypes = {adtType | defType(AType adtType) <- overloadedDefs.defInfo}; + set[loc] constructorDefs = {d + | <- (rascalReachableDefs(ws, overloadedDefs.defined))[constructorId()] + , adtType in adtTypes + , any(dd <- overloadedDefs.defined, isStrictlyContainedIn(d, dd)) + }; + + set[Define] defsReachableFromOverloads = rascalReachableDefs(ws, overloadedDefs.defined + defUseScopes[overloadedDefs.defined]); + set[Define] defsReachableFromOverloadConstructors = rascalReachableDefs(ws, constructorDefs + defUseScopes[constructorDefs]); + + rel[loc scope, loc def] selectedADTs = { + + | <- ((defsReachableFromOverloads + defsReachableFromOverloadConstructors))[role] + , adtType in adtTypes + }; + + rel[loc fromScope, loc toScope] adtStep = (selectedADTs + invert(defUseScopes)) o defPathStep; + defPathStep = defPathStep + + (defPathStep o adtStep+); + defPaths = defPathStep o selectedADTs; + } else if (fieldId() := role) { + // We are looking for fields for the same ADT type (but not necessarily same constructor type) + set[DefInfo] selectedADTTypes = (ws.defines)[overloadedDefs.scope]; + rel[loc, loc] selectedADTs = (ws.defines)[selectedADTTypes]; + rel[loc, loc] selectedFields = selectedADTs o ws.defines; + defPaths = defPathStep o selectedFields; + } else { + // Find definitions in the reached scope, and definitions within those definitions (transitively) + defPaths = defPathStep o invert(defScopes); + } - set[loc] reachableDefs = defPaths[overloadedDefs]; - solve(overloadedDefs, reachableDefs) { - overloadedDefs += {d - | loc d <- reachableDefs - , mayOverloadF(overloadedDefs + d, ws.definitions) + set[loc] overloadCandidates = defPaths[overloadedDefs.defined]; + overloadedDefs += {ws.definitions[d] + | loc d <- overloadCandidates + , mayOverloadF(overloadedDefs.defined + d, ws.definitions) }; - reachableDefs = defPaths[overloadedDefs]; + reachableDefs = rascalReachableDefs(ws, overloadedDefs.defined).defined; } - return overloadedDefs; + return overloadedDefs.defined; } private rel[loc, loc] NO_RENAMES(str _) = {}; diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc new file mode 100644 index 000000000..cbad9b12c --- /dev/null +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc @@ -0,0 +1,247 @@ +@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::Constructors + +import lang::rascal::tests::rename::TestUtils; + +test bool extendedConstructor() = testRenameOccurrences({ + byText("Definer", "data Foo = foo(int i);", {0}) + , byText("Extender", + "extend Definer; + 'data Foo = foo(int i, int j); + 'void main() { + ' Foo f = foo(8); + '}", {0, 1}) +}); + +test bool disjunctConstructor() = testRenameOccurrences({ + byText("Definer", "data Foo = foo(int i);", {0}) + , byText("Unrelated", + "data Foo = foo();", {}) +}); + +test bool differentADTsDuplicateConstructorNames() = testRenameOccurrences({ + byText("A", "data Bar = foo();", {0}) + , byText("B", + "extend A; + 'data Foo = foo(int i); + 'Bar f = foo();", {1}) +}); + +test bool constructorNameUsedAsVar() = testRenameOccurrences({ + byText("Constructor", "data Foo = foo();", {0}) + , byText("OtherType", + "import Constructor; + 'int foo = 8;", {}) +}); + +test bool constructorsAndTypesInVModuleStructure() = testRenameOccurrences({ + byText("Left", "data Foo = foo();", {0}), byText("Right", "data Foo = foo(int i);", {0}) + , byText("Merger", + "import Left; + 'import Right; + 'bool f(Foo f) = (f == foo() || f == foo(1)); + ", {0, 1}) +}); + +test bool constructorsVModuleStructure() = testRenameOccurrences({ + byText("Left", "data Foo = foo();", {0}), byText("Right", "data Foo = foo(int i);", {0}) + , byText("Merger", + "import Left; + 'import Right; + 'bool f(foo()) = true; + ", {0}) +}); + +@synopsis{ + (defs) + / \ + A B C + \/ \/ + D E + / \ + (uses) +} +test bool constructorsAndTypesInWModuleStructureWithoutMerge() = testRenameOccurrences({ + byText("A", "data Foo = foo();", {0}), byText("B", "", {}), byText("C", "data Foo = foo(int i, int j);", {}) + , byText("D", + "import A; + 'import B; + 'bool func(Foo f) = f == foo(); + ", {0}), byText("E", + "import B; + 'import C; + 'bool func(Foo f) = f == foo(8, 54);", {}) +}); + +@synopsis{ + (defs) + / \ + A B C + \/ \/ + D E + / \ + (uses) +} +test bool constructorsInWModuleStructureWithoutMerge() = testRenameOccurrences({ + byText("A", "data Foo = foo();", {0}), byText("B", "", {}), byText("C", "data Foo = foo(int i, int j);", {}) + , byText("D", + "import A; + 'import B; + 'bool func(foo()) = true; + ", {0}), byText("E", + "import B; + 'import C; + 'bool func(foo(8, 54)) = true;", {}) +}); + +@synopsis{ + (defs) + / | \ + v v v + A B C + \/ \/ + D E + ^ ^ + / \ + (uses) +} +test bool constructorsAndTypesInWModuleStructureWithMerge() = testRenameOccurrences({ + byText("A", "data Foo = foo();", {0}), byText("B", "data Foo = foo(int i);", {0}), byText("C", "data Foo = foo(int i, int j);", {0}) + , byText("D", + "import A; + 'import B; + 'bool func(Foo f) = f == foo(); + ", {0}), byText("E", + "import B; + 'import C; + 'bool func(Foo f) = f == foo(8, 54);", {0}) +}); + +@synopsis{ + (defs) + / | \ + v v v + A B C + \/ \/ + D E + ^ ^ + / \ + (uses) +} +test bool constructorsInWModuleStructureWithMerge() = testRenameOccurrences({ + byText("A", "data Foo = foo();", {0}), byText("B", "data Foo = foo(int i);", {0}), byText("C", "data Foo = foo(int i, int j);", {0}) + , byText("D", + "import A; + 'import B; + 'bool func(foo()) = true; + ", {0}), byText("E", + "import B; + 'import C; + 'bool func(foo(8, 54)) = true;", {0}) +}); + +test bool constructorsAndTypesInYModuleStructure() = testRenameOccurrences({ + byText("Left", "data Foo = foo();", {0}), byText("Right", "data Foo = foo(int i);", {0}) + , byText("Merger", + "extend Left; + 'extend Right; + ", {}) + , byText("User", + "import Merger; + 'bool f(Foo f) = (f == foo() || f == foo(1)); + ", {0, 1}) +}); + +test bool constructorsInYModuleStructure() = testRenameOccurrences({ + byText("Left", "data Foo = foo();", {0}), byText("Right", "data Foo = foo(int i);", {0}) + , byText("Merger", + "extend Left; + 'extend Right; + ", {}) + , byText("User", + "import Merger; + 'bool f(foo()) = true; + 'bool f(foo(1)) = true; + ", {0, 1}) +}); + +test bool constructorsAndTypesInInvertedVModuleStructure() = testRenameOccurrences({ + byText("Definer", "data Foo = foo() | foo(int i);", {0, 1}), + byText("Left", "import Definer; + 'bool isF(Foo f) = f == foo();", {0}), byText("Right", "import Definer; + 'bool isG(Foo f) = f == foo(1);", {0}) +}); + +test bool constructorsInInvertedVModuleStructure() = testRenameOccurrences({ + byText("Definer", "data Foo = foo() | foo(int i);", {0, 1}), + byText("Left", "import Definer; + 'bool isF(foo()) = true;", {0}), byText("Right", "import Definer; + 'bool isG(foo(1)) = true;", {0}) +}); + +test bool constructorsAndTypesInDiamondModuleStructure() = testRenameOccurrences({ + byText("Definer", "data Foo = foo() | foo(int i);", {0, 1}), + byText("Left", "extend Definer;", {}), byText("Right", "extend Definer;", {}), + byText("User", "import Left; + 'import Right; + 'bool isF(Foo f) = f == foo(); + 'bool isG(Foo f) = f == foo(8);", {0, 1}) +}); + +test bool constructorsInDiamondModuleStructure() = testRenameOccurrences({ + byText("Definer", "data Foo = foo() | foo(int i);", {0, 1}), + byText("Left", "extend Definer;", {}), byText("Right", "extend Definer;", {}), + byText("User", "import Left; + 'import Right; + 'bool isF(foo()) = true; + 'bool isG(foo(8)) = true;", {0, 1}) +}); + +@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 constructorsAndTypesInIIModuleStructure() = testRenameOccurrences({ + byText("LeftDefiner", "data Foo = foo();", {0}), byText("RightDefiner", "data Foo = foo(int i);", {}), + byText("LeftExtender", "extend LeftDefiner;", {}), byText("RightExtender", "extend RightDefiner;", {}), + byText("LeftUser", "import LeftExtender; + 'bool func(Foo f) = f == foo();", {0}), byText("RightUser", "import RightExtender; + 'bool func(Foo f) = f == foo(8);", {}) +}); + +@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 constructorsInIIModuleStructure() = testRenameOccurrences({ + byText("LeftDefiner", "data Foo = foo();", {0}), byText("RightDefiner", "data Foo = foo(int i);", {}), + byText("LeftExtender", "extend LeftDefiner;", {}), byText("RightExtender", "extend RightDefiner;", {}), + byText("LeftUser", "import LeftExtender; + 'bool func(foo()) = true;", {0}), byText("RightUser", "import RightExtender; + 'bool func(foo(8)) = true;", {}) +}); From fe7845a0cbdf851449dceaeef2dc88e090736187 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 29 Oct 2024 10:06:40 +0100 Subject: [PATCH 14/14] Tests for constructor patterns and `is` check. --- .../lang/rascal/tests/rename/Constructors.rsc | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc index cbad9b12c..b81eab7a1 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/tests/rename/Constructors.rsc @@ -59,6 +59,27 @@ test bool constructorNameUsedAsVar() = testRenameOccurrences({ 'int foo = 8;", {}) }); +test bool constructorInPattern() = testRenameOccurrences({0, 1, 2, 3}, " + 'Foo f = foo(8); + 'if (foo(_) := f) { + ' int x = match(f); + '} +", decls = " + 'data Foo = foo(int i); + 'int match(foo(int i)) = i; +"); + +test bool constructorIsCheck() = testRenameOccurrences({0, 1, 2, 3}, " + 'Foo f = foo(8); + 'isFoo(f); +", decls = " + 'data Foo + ' = foo(int i) + ' | foo(int i, int j) + ' | baz(); + bool isFoo(Foo f) = f is foo; +"); + test bool constructorsAndTypesInVModuleStructure() = testRenameOccurrences({ byText("Left", "data Foo = foo();", {0}), byText("Right", "data Foo = foo(int i);", {0}) , byText("Merger",