Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- Fixed missing registered capability for `textDocument/documentHighlight`
([#421](https://github.com/fortran-lang/fortls/issues/421s))

- Fixed `textDocument/rename` not renaming the implicit result variable of a
function declared without a `RESULT()` clause
([#322](https://github.com/fortran-lang/fortls/issues/322))

## 3.2.2

### Fixed
Expand Down
21 changes: 21 additions & 0 deletions fortls/langserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,27 @@ def get_all_references(
# A container that includes all the FQSN signatures for objects that
# are linked to the rename request and that should also be replaced
override_cache: list[str] = []
# A function without an explicit RESULT() clause returns through an
# implicit result variable that shares the function's name but is a
# distinct object with its own FQSN, e.g. `mod::fun` vs
# `mod::fun::fun`. The two must always be renamed together or the code
# stops compiling, so link them in both directions.
result_obj = getattr(def_obj, "result_obj", None)
if (
def_obj.get_type() == FUNCTION_TYPE_ID
and result_obj is not None
and def_obj.result_name.lower() == def_name
):
override_cache.append(result_obj.FQSN)
else:
parent = getattr(def_obj, "parent", None)
if (
parent is not None
and parent.get_type() == FUNCTION_TYPE_ID
and getattr(parent, "result_obj", None) is def_obj
and parent.result_name.lower() == parent.name.lower()
):
override_cache.append(parent.FQSN)
refs = {}
ref_objs = []
for filename, file_obj in file_set:
Expand Down
60 changes: 60 additions & 0 deletions test/test_server_rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,63 @@ def test_rename_skip_intrinsic():
errcode, results = run_request(string, ["-n", "1"])
# FIXME: to be implemented
assert errcode == 0


def test_rename_implicit_function_result():
"""Test that renaming a function without a RESULT() clause also renames the
implicit result variable in the function body, see issue #322.
"""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "rename")})
file_path = test_dir / "rename" / "test_rename_implicit_result.f90"
string += rename_request("sin_deg", file_path, 7, 25)
errcode, results = run_request(string, ["-n", "1"])
assert errcode == 0
ref = {}
ref[path_to_uri(str(file_path))] = [
create("sin_deg", 7, 23, 7, 27),
create("sin_deg", 9, 8, 9, 12),
]
# zip() in check_rename_response silently ignores missing changes,
# so assert the count explicitly
assert len(results[1]["changes"][path_to_uri(str(file_path))]) == 2
check_rename_response(results[1]["changes"], ref)


def test_rename_implicit_function_result_from_body():
"""Test that renaming from the implicit result variable in the body also
renames the function definition, see issue #322.
"""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "rename")})
file_path = test_dir / "rename" / "test_rename_implicit_result.f90"
string += rename_request("sin_deg", file_path, 9, 9)
errcode, results = run_request(string, ["-n", "1"])
assert errcode == 0
ref = {}
ref[path_to_uri(str(file_path))] = [
create("sin_deg", 7, 23, 7, 27),
create("sin_deg", 9, 8, 9, 12),
]
# zip() in check_rename_response silently ignores missing changes,
# so assert the count explicitly
assert len(results[1]["changes"][path_to_uri(str(file_path))]) == 2
check_rename_response(results[1]["changes"], ref)


def test_rename_explicit_function_result():
"""Test that a function with an explicit RESULT() clause only renames the
result variable, not the function name, see issue #322.
"""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "rename")})
file_path = test_dir / "rename" / "test_rename_implicit_result.f90"
string += rename_request("res", file_path, 16, 9)
errcode, results = run_request(string, ["-n", "1"])
assert errcode == 0
ref = {}
ref[path_to_uri(str(file_path))] = [
create("res", 14, 39, 14, 40),
create("res", 16, 8, 16, 9),
]
# zip() in check_rename_response silently ignores missing changes,
# so assert the count explicitly
assert len(results[1]["changes"][path_to_uri(str(file_path))]) == 2
check_rename_response(results[1]["changes"], ref)
20 changes: 20 additions & 0 deletions test/test_source/rename/test_rename_implicit_result.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module test_rename_implicit_result
implicit none
contains

! No RESULT() clause: the function returns through an implicit result
! variable that shares the function's name. Renaming the function must
! rename the assignment below too. See issue #322.
real pure function sind(x)
real, intent(in), value :: x
sind = sin(x*((4.0*atan(1.0))/180.0))
end function

! Explicit RESULT() clause: the function name is not a variable in the
! body, so renaming the function must NOT touch `r`.
real function withresult(x) result(r)
real, intent(in) :: x
r = x*3.0
end function

end module test_rename_implicit_result
Loading