Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename refactoring for grammars #418

Merged
merged 34 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
08dfef9
Implement & test grammars.
toinehartman Aug 1, 2024
fde839a
Implement nonterminal type parameters.
toinehartman Aug 2, 2024
95e4f5e
Implement nonterminal fields.
toinehartman Aug 2, 2024
04b0eec
Implement renaming of constructor excepts.
toinehartman Aug 2, 2024
953887f
Test more grammar constructs.
toinehartman Aug 2, 2024
850b6b3
Remove unneccesary constructor label from tests.
toinehartman Aug 2, 2024
fa6f779
Improve error on except constructor rename.
toinehartman Aug 5, 2024
91a1c75
Merge remote-tracking branch 'origin/feature/rename-refactoring/cross…
toinehartman Aug 20, 2024
218ab24
Adapt grammar tests to updated test fixtures.
toinehartman Aug 21, 2024
2692f92
Add metavariable test.
toinehartman Aug 21, 2024
44f4e6c
Use empty data definition instead of import for performance.
toinehartman Aug 21, 2024
1067f86
Merge remote-tracking branch 'origin/main' into feature/rename-refact…
toinehartman Sep 5, 2024
786a6b7
Find field definition in ADT/non-terminal.
toinehartman Sep 5, 2024
0dc06dd
Add multi-module grammar tests.
toinehartman Sep 6, 2024
7d6a26b
Record existence of all source files for 'outside workspace' check.
toinehartman Sep 6, 2024
e37ca5b
Use resolved ADT location instead of container loc.
toinehartman Sep 9, 2024
f8e6722
Support except production as cursor.
toinehartman Sep 11, 2024
1d018b5
Fix memo annotations.
toinehartman Sep 11, 2024
d8aecb8
Use single-digit test numbers to simplify locaiton comparison.
toinehartman Sep 11, 2024
cf68cfd
Merge remote-tracking branch 'origin/main' into feature/rename-refact…
toinehartman Sep 11, 2024
9a7437f
Remove unused import.
toinehartman Sep 11, 2024
09599df
Stricter check for valid symbol names.
toinehartman Sep 11, 2024
cbf42c0
Apply suggestions from code review
toinehartman Oct 2, 2024
86ae6cc
Merge branch 'main' into feature/rename-refactoring/grammars
toinehartman Oct 2, 2024
c66c1d1
Document complex module structure tests.
toinehartman Oct 2, 2024
3250903
Document overloading of ADTs/grammars.
toinehartman Oct 2, 2024
537db4c
Elaborate on overloaded ADTs.
toinehartman Oct 3, 2024
b72d60d
Merge branch 'main' into feature/rename-refactoring/grammars
toinehartman Oct 3, 2024
2dee9f0
Merge branch 'main' into feature/rename-refactoring/grammars
toinehartman Oct 4, 2024
489b9fb
Merge branch 'main' into feature/rename-refactoring/grammars
toinehartman Oct 4, 2024
ed782c3
Bump versions to newest Rascal release.
toinehartman Oct 4, 2024
d91efd5
Always clean test dir before single-module test.
toinehartman Oct 7, 2024
825d30c
Merge remote-tracking branch 'origin/main' into feature/rename-refact…
toinehartman Oct 7, 2024
ff97330
Merge branch 'main' into feature/rename-refactoring/grammars
toinehartman Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import Set;
alias Capture = tuple[loc def, loc use];

data IllegalRenameReason
= invalidName(str name)
= invalidName(str name, str identifierDescription)
| doubleDeclaration(loc old, set[loc] new)
| captureChange(set[Capture] captures)
| definitionsOutsideWorkspace(set[loc] defs)
Expand All @@ -46,7 +46,7 @@ data RuntimeException
| unexpectedFailure(str message)
;

str describe(invalidName(name)) = "\'<name>\' is not a valid identifier";
str describe(invalidName(name, idDescription)) = "\'<name>\' is not a valid <idDescription>";
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";
Expand Down
89 changes: 72 additions & 17 deletions rascal-lsp/src/main/rascal/lang/rascal/lsp/refactor/Rename.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,35 @@ void throwAnyErrors(program(_, msgs)) {
throwAnyErrors(msgs);
}

set[IllegalRenameReason] rascalCheckLegalName(str name) {
try {
parse(#Name, rascalEscapeName(name));
return {};
} catch ParseError(_): {
return {invalidName(name)};
set[IllegalRenameReason] rascalCheckLegalName(str name, set[IdRole] defRoles) {
set[IllegalRenameReason] tryParseAs(type[&T <: Tree] begin, str idDescription) {
try {
parse(begin, rascalEscapeName(name));
return {};
} catch ParseError(_): {
return {invalidName(name, idDescription)};
}
}

bool isSyntaxRole = any(role <- defRoles, role in syntaxRoles);
bool isField = any(role <- defRoles, role is fieldId);
bool isConstructor = any(role <- defRoles, role is constructorId);

set[IllegalRenameReason] reasons = {};
if (isSyntaxRole) {
reasons += tryParseAs(#Nonterminal, "non-terminal name");
}
if (isField) {
reasons += tryParseAs(#NonterminalLabel, "constructor field name");
}
if (isConstructor) {
reasons += tryParseAs(#NonterminalLabel, "constructor name");
}
if (!(isSyntaxRole || isField || isConstructor)) {
reasons += tryParseAs(#Name, "identifier");
}

return reasons;
}

private set[IllegalRenameReason] rascalCheckDefinitionsOutsideWorkspace(WorkspaceInfo ws, set[loc] defs) =
Expand Down Expand Up @@ -165,7 +187,7 @@ private set[IllegalRenameReason] rascalCollectIllegalRenames(WorkspaceInfo ws, s
set[Define] newNameDefs = {def | Define def:<_, newName, _, _, _, _> <- ws.defines};

return
rascalCheckLegalName(newName)
rascalCheckLegalName(newName, definitionsRel(ws)[currentDefs].idRole)
+ rascalCheckDefinitionsOutsideWorkspace(ws, currentDefs)
+ rascalCheckCausesDoubleDeclarations(ws, currentDefs, newNameDefs, newName)
+ rascalCheckCausesCaptures(ws, m, currentDefs, currentUses, newNameDefs)
Expand Down Expand Up @@ -206,6 +228,10 @@ Maybe[loc] rascalLocationOfName(Declaration d) = rascalLocationOfName(d.user.nam
|| d is \data;
Maybe[loc] rascalLocationOfName(TypeVar tv) = just(tv.name.src);
Maybe[loc] rascalLocationOfName(Header h) = rascalLocationOfName(h.name);
Maybe[loc] rascalLocationOfName(SyntaxDefinition sd) = rascalLocationOfName(sd.defined);
Maybe[loc] rascalLocationOfName(Sym sym) = just(sym.nonterminal.src);
Maybe[loc] rascalLocationOfName(Nonterminal nt) = just(nt.src);
Maybe[loc] rascalLocationOfName(NonterminalLabel l) = just(l.src);
default Maybe[loc] rascalLocationOfName(Tree t) = nothing();

private tuple[set[IllegalRenameReason] reasons, list[TextEdit] edits] computeTextEdits(WorkspaceInfo ws, start[Module] m, set[loc] defs, set[loc] uses, str name) {
Expand Down Expand Up @@ -238,7 +264,7 @@ private bool rascalIsFunctionLocal(WorkspaceInfo ws, cursor(use(), cursorLoc, _)
private bool rascalIsFunctionLocal(WorkspaceInfo _, cursor(typeParam(), _, _)) = true;
private default bool rascalIsFunctionLocal(_, _) = false;

Maybe[AType] rascalAdtCommonKeywordFieldType(WorkspaceInfo ws, str fieldName, Define _:<_, _, _, dataId(), _, DefInfo defInfo>) {
Maybe[AType] rascalAdtCommonKeywordFieldType(WorkspaceInfo ws, str fieldName, Define _:<_, _, _, _, _, DefInfo defInfo>) {
if (defInfo.commonKeywordFields?
, kwf:(KeywordFormal) `<Type _> <Name kwName> = <Expression _>` <- defInfo.commonKeywordFields
, "<kwName>" == fieldName) {
Expand Down Expand Up @@ -266,7 +292,7 @@ private CursorKind rascalGetDataFieldCursorKind(WorkspaceInfo ws, loc container,
return dataCommonKeywordField(dt.defined, fieldType);
}

for (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- ws.defines) {
for (Define d: <_, _, _, constructorId(), _, defType(acons(adtType, _, _))> <- rascalReachableDefs(ws, {dt.defined})) {
if (just(fieldType) := rascalConsKeywordFieldType(cursorName, d)) {
// Case 3 (or 0): keyword field
return dataKeywordField(dt.defined, fieldType);
Expand All @@ -275,6 +301,10 @@ private CursorKind rascalGetDataFieldCursorKind(WorkspaceInfo ws, loc container,
return dataField(dt.defined, fieldType);
}
}

if (Define d: <_, cursorName, _, fieldId(), _, defType(adtType)> <- rascalReachableDefs(ws, {dt.defined})) {
return dataField(dt.defined, d.defInfo.atype);
}
}

set[loc] fromDefs = cursorLoc in ws.useDef<1> ? {cursorLoc} : getDefs(ws, cursorLoc);
Expand Down Expand Up @@ -392,6 +422,8 @@ private Cursor rascalGetCursor(WorkspaceInfo ws, Tree cursorT) {
, <smallestKeywordContainingCursor, keywordParam()>
// Module name declaration, where the cursor location is in the module header
, <flatMap(rascalLocationOfName(parseModuleWithSpacesCached(cursorLoc.top).top.header), Maybe[loc](loc nameLoc) { return isContainedIn(cursorLoc, nameLoc) ? just(nameLoc) : nothing(); }), moduleName()>
// Nonterminal constructor names in exception productions
, <findSmallestContaining({l | l <- ws.facts, at := ws.facts[l], (at is conditional || aprod(prod(_, /conditional(_, _))) := at), /\a-except(cursorName) := at}, cursorLoc), exceptConstructor()>
}
};

Expand Down Expand Up @@ -447,14 +479,36 @@ private bool rascalContainsName(loc l, str name) {
*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.
Functions are 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] _) = _;
```

ADT and grammar definitions are considered overloaded when they have the same name and type, and
there is a common use from which they are reachable.
As an example, modules `A` and `B` have a definition for ADT `D`:
```
module A
data D = a();
```
```
module B
data D = b();
```
With no other modules in the workspace, renaming `D` in one of those modules, will not rename `D` in
the other module, as they are not considered an overloaded definition. However, if a third module `C`
exists, that imports both and uses the definition, the definitions will be considered overloaded, and
renaming `D` from either module `A`, `B` or `C` will result in renaming all occurrences.
```
module C
import A;
import B;
D f() = a();
```

*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.
Expand All @@ -474,20 +528,21 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s
ProjectFiles() {
return { <
max([f | f <- workspaceFolders, isPrefixOf(f, cursorLoc)]),
cursorLoc.top
cursorLoc.top,
true
> };
},
// Full load
ProjectFiles() {
return { <folder, file>
return {
// If we do not find any occurrences of the name under the cursor in a module,
// we are not interested in loading the model, but we still want to inform the
// renaming framework about the existence of the file.
<folder, file, rascalContainsName(file, cursorName)>
| folder <- workspaceFolders
, PathConfig pcfg := getPathConfig(folder)
, srcFolder <- pcfg.srcs
, file <- find(srcFolder, "rsc")
, file != cursorLoc.top // because we loaded that during preload
// 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
Expand All @@ -499,7 +554,7 @@ list[DocumentEdit] rascalRenameSymbol(Tree cursorT, set[loc] workspaceFolders, s
RascalCompilerConfig ccfg = rascalCompilerConfig(pcfg)[forceCompilationTopModule = true]
[verbose = false]
[logPathConfig = false];
for (file <- \files) {
for (<file, true> <- \files) {
ms = rascalTModelForLocs([file], ccfg, dummy_compile1);
tmodels += {convertTModel2PhysicalLocs(tm) | m <- ms.tmodels, tm := ms.tmodels[m]};
}
Expand Down
Loading
Loading