Skip to content

Commit 8c2460b

Browse files
committed
Standardises function hover
- Makes hover more robust - Adds extensive unittests for hover support of functions - Improves documentation of function hover
1 parent acc33b9 commit 8c2460b

File tree

5 files changed

+135
-31
lines changed

5 files changed

+135
-31
lines changed

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
## 2.2.2
44

5+
### Changed
6+
7+
- Changed the way function hover messages are displayed, now signatures are standardised
8+
([gnikit/fortls#47](https://github.com/gnikit/fortls/issues/47))
9+
510
### Fixed
611

712
- Fixed hovering over functions displaying as theire result types
813
([gnikit/fortls#22](https://github.com/gnikit/fortls/issues/22))
9-
- Fixed function hovering signature now standardised
10-
([#47](https://github.com/hansec/fortran-language-server/issues/47))
1114
- Fixed function modifiers not displaying upon hover
1215
([gnikit/fortls#48](https://github.com/gnikit/fortls/issues/48))
16+
- Fixed function hover when returning arrays
17+
([gnikit/fortls#50](https://github.com/gnikit/fortls/issues/50))
1318

1419
## 2.2.1
1520

fortls/objects.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ def get_hover(self, long=False, include_doc=True, drop_arg=-1):
976976
keyword_list = get_keywords(self.keywords)
977977
keyword_list.append(f"{self.get_desc()} ")
978978
hover_array = [" ".join(keyword_list) + sub_sig]
979-
self.get_docs_full(hover_array, long, include_doc, drop_arg)
979+
hover_array = self.get_docs_full(hover_array, long, include_doc, drop_arg)
980980
return "\n ".join(hover_array), long
981981

982982
def get_docs_full(
@@ -994,6 +994,7 @@ def get_docs_full(
994994
doc_str = arg_obj.get_documentation()
995995
if include_doc and (doc_str is not None):
996996
hover_array += doc_str.splitlines()
997+
return hover_array
997998

998999
def get_signature(self, drop_arg=-1):
9991000
arg_sigs = []
@@ -1136,22 +1137,63 @@ def get_desc(self):
11361137
def is_callable(self):
11371138
return False
11381139

1139-
def get_hover(self, long=False, include_doc=True, drop_arg=-1):
1140+
def get_hover(
1141+
self, long: bool = False, include_doc: bool = True, drop_arg: int = -1
1142+
) -> tuple[str, bool]:
1143+
"""Construct the hover message for a FUNCTION.
1144+
Two forms are produced here the `long` i.e. the normal for hover requests
1145+
1146+
```
1147+
[MODIFIERS] FUNCTION NAME([ARGS]) RESULT(RESULT_VAR)
1148+
TYPE, [ARG_MODIFIERS] :: [ARGS]
1149+
TYPE, [RESULT_MODIFIERS] :: RESULT_VAR
1150+
```
1151+
1152+
note: intrinsic functions will display slightly different,
1153+
`RESULT_VAR` and its `TYPE` might not always be present
1154+
1155+
short form, used when functions are arguments in functions and subroutines:
1156+
1157+
```
1158+
FUNCTION NAME([ARGS]) :: ARG_LIST_NAME
1159+
```
1160+
1161+
Parameters
1162+
----------
1163+
long : bool, optional
1164+
toggle between long and short hover results, by default False
1165+
include_doc : bool, optional
1166+
if to include any documentation, by default True
1167+
drop_arg : int, optional
1168+
Ignore argument at position `drop_arg` in the argument list, by default -1
1169+
1170+
Returns
1171+
-------
1172+
tuple[str, bool]
1173+
String representative of the hover message and the `long` flag used
1174+
"""
11401175
fun_sig, _ = self.get_snippet(drop_arg=drop_arg)
1141-
fun_sig += f" RESULT({self.result_name})"
1142-
keyword_list = []
1143-
if self.result_type:
1144-
keyword_list.append(self.result_type)
1145-
keyword_list += get_keywords(self.keywords)
1176+
# short hover messages do not include the result()
1177+
fun_sig += f" RESULT({self.result_name})" if long else ""
1178+
keyword_list = get_keywords(self.keywords)
11461179
keyword_list.append("FUNCTION")
11471180

11481181
hover_array = [f"{' '.join(keyword_list)} {fun_sig}"]
1149-
self.get_docs_full(hover_array, long, include_doc, drop_arg)
1182+
hover_array = self.get_docs_full(hover_array, long, include_doc, drop_arg)
1183+
# Only append the return value if using long form
1184+
if self.result_obj and long:
1185+
arg_doc, _ = self.result_obj.get_hover(include_doc=False)
1186+
hover_array.append(f"{arg_doc} :: {self.result_obj.name}")
1187+
# intrinsic functions, where the return type is missing but can be inferred
1188+
elif self.result_type and long:
1189+
# prepend type to function signature
1190+
hover_array[0] = f"{self.result_type} {hover_array[0]}"
11501191
return "\n ".join(hover_array), long
11511192

11521193
def get_interface(self, name_replace=None, change_arg=-1, change_strings=None):
11531194
fun_sig, _ = self.get_snippet(name_replace=name_replace)
11541195
fun_sig += f" RESULT({self.result_name})"
1196+
# XXX:
11551197
keyword_list = []
11561198
if self.result_type:
11571199
keyword_list.append(self.result_type)

fortls/parse_fortran.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def read_var_def(line: str, type_word: str = None, fun_only: bool = False):
290290

291291

292292
def read_fun_def(
293-
line: str, result: RESULT_sig = RESULT_sig(), mod_flag: bool = False
293+
line: str, result: RESULT_sig = None, mod_flag: bool = False
294294
) -> tuple[Literal["fun"], FUN_sig] | None:
295295
"""Attempt to read FUNCTION definition line
296296
@@ -345,9 +345,11 @@ def read_fun_def(
345345
# Extract if possible the variable name of the result()
346346
trailing_line = trailing_line.strip()
347347
results_match = RESULT_REGEX.match(trailing_line)
348+
if result is None:
349+
result = RESULT_sig()
348350
if results_match:
349351
result.name = results_match.group(1).strip().lower()
350-
return "fun", FUN_sig(name, args, keywords, mod_flag, result=result)
352+
return "fun", FUN_sig(name, args, keywords, mod_flag, result)
351353

352354

353355
def read_sub_def(line: str, mod_flag: bool = False):

test/test_server.py

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,11 @@ def check_return(result_array, checks):
579579
string += hover_req(file_path, 12, 12)
580580
string += hover_req(file_path, 18, 19)
581581
string += hover_req(file_path, 23, 34)
582+
string += hover_req(file_path, 28, 11)
583+
string += hover_req(file_path, 34, 21)
584+
string += hover_req(file_path, 46, 11)
585+
string += hover_req(file_path, 51, 11)
586+
string += hover_req(file_path, 55, 11)
582587
file_path = test_dir / "hover" / "recursive.f90"
583588
string += hover_req(file_path, 9, 40)
584589
file_path = test_dir / "subdir" / "test_submod.F90"
@@ -604,34 +609,54 @@ def check_return(result_array, checks):
604609
"DOUBLE PRECISION, PARAMETER :: somevar = 23.12",
605610
"DOUBLE PRECISION, PARAMETER :: some = 1e-19",
606611
"INTEGER, POINTER",
607-
"""INTEGER FUNCTION fun1(arg) RESULT(fun1)
608-
INTEGER, INTENT(IN) :: arg""",
609-
"""INTEGER FUNCTION fun2(arg) RESULT(fun2)
610-
INTEGER, INTENT(IN) :: arg""",
611-
"""INTEGER FUNCTION fun3(arg) RESULT(retval)
612-
INTEGER, INTENT(IN) :: arg""",
613-
"""INTEGER FUNCTION fun4(arg) RESULT(retval)
614-
INTEGER, INTENT(IN) :: arg""",
612+
"""FUNCTION fun1(arg) RESULT(fun1)
613+
INTEGER, INTENT(IN) :: arg
614+
INTEGER :: fun1""",
615+
"""FUNCTION fun2(arg) RESULT(fun2)
616+
INTEGER, INTENT(IN) :: arg
617+
INTEGER :: fun2""",
618+
"""FUNCTION fun3(arg) RESULT(retval)
619+
INTEGER, INTENT(IN) :: arg
620+
INTEGER :: retval""",
621+
"""FUNCTION fun4(arg) RESULT(retval)
622+
INTEGER, INTENT(IN) :: arg
623+
INTEGER :: retval""",
615624
# Notice that the order of the modifiers does not match the source code
616625
# This is part of the test, ideally they would be identical but previously
617626
# any modifiers before the type would be discarded
618-
"""INTEGER PURE ELEMENTAL FUNCTION fun5(arg) RESULT(retval)
619-
INTEGER, INTENT(IN) :: arg""",
627+
"""PURE ELEMENTAL FUNCTION fun5(arg) RESULT(retval)
628+
INTEGER, INTENT(IN) :: arg
629+
INTEGER :: retval""",
630+
"""FUNCTION fun6(arg) RESULT(retval)
631+
INTEGER, INTENT(IN) :: arg
632+
INTEGER, DIMENSION(10,10) :: retval""",
633+
"""PURE FUNCTION outer_product(x, y) RESULT(outer_product)
634+
REAL, DIMENSION(:), INTENT(IN) :: x
635+
REAL, DIMENSION(:), INTENT(IN) :: y
636+
REAL, DIMENSION(SIZE(X), SIZE(Y)) :: outer_product""",
637+
"""FUNCTION dlamch(cmach) RESULT(dlamch)
638+
CHARACTER :: CMACH""",
639+
"""FUNCTION fun7() RESULT(val)
640+
TYPE(c_ptr) :: val""",
641+
"""TYPE(c_ptr) FUNCTION c_loc(x) RESULT(c_loc)""",
620642
"""RECURSIVE SUBROUTINE recursive_assign_descending(node, vector, current_loc)
621643
TYPE(tree_inode), POINTER, INTENT(IN) :: node
622644
INTEGER, DIMENSION(:), INTENT(INOUT) :: vector
623645
INTEGER, INTENT(INOUT) :: current_loc""",
624-
# TODO: more tests to add from functions
625-
"""REAL FUNCTION point_dist(a, b) RESULT(distance)
646+
"""FUNCTION point_dist(a, b) RESULT(distance)
626647
TYPE(point), INTENT(IN) :: a
627-
TYPE(point), INTENT(IN) :: b""",
628-
"""LOGICAL FUNCTION is_point_equal_a(a, b) RESULT(is_point_equal_a)
648+
TYPE(point), INTENT(IN) :: b
649+
REAL :: distance""",
650+
"""FUNCTION is_point_equal_a(a, b) RESULT(is_point_equal_a)
629651
TYPE(point), INTENT(IN) :: a
630-
TYPE(point), INTENT(IN) :: b""",
631-
"""REAL FUNCTION foo2(f, g, h) RESULT(arg3)
632-
REAL FUNCTION f(x) RESULT(z) :: f
633-
REAL FUNCTION g(x) RESULT(z) :: g
634-
REAL FUNCTION h(x) RESULT(z) :: h""",
652+
TYPE(point), INTENT(IN) :: b
653+
LOGICAL :: is_point_equal_a""",
654+
# Could be subject to change
655+
"""FUNCTION foo2(f, g, h) RESULT(arg3)
656+
FUNCTION f(x) :: f
657+
FUNCTION g(x) :: g
658+
FUNCTION h(x) :: h
659+
REAL :: arg3""",
635660
)
636661
assert len(ref_results) == len(results) - 1
637662
check_return(results[1:], ref_results)

test/test_source/hover/functions.f90

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,33 @@ pure integer elemental function fun5(arg) result(retval)
2525
integer, intent(in) :: arg
2626
end function fun5
2727

28+
! function with type on definition and return
29+
function fun6(arg) result(retval)
30+
integer, intent(in) :: arg
31+
integer, dimension(10,10) :: retval
32+
end function fun6
33+
34+
! functions with complex result type
35+
pure function outer_product(x, y)
36+
real, dimension(:), intent(in) :: x, y
37+
real, dimension(size(x), size(y)) :: outer_product
38+
integer :: i, j
39+
forall (i=1:size(x))
40+
forall (j=1:size(y))
41+
outer_product(i, j) = x(i) * y(j)
42+
end forall
43+
end forall
44+
end function outer_product
45+
46+
! functions with no result type, common in interfaces
47+
function dlamch(CMACH)
48+
character :: CMACH
49+
end function dlamch
50+
51+
! intrinsic functions like c_loc display a return type
52+
function fun7() result(val)
53+
use, intrinsic :: iso_c_binding
54+
integer, dimension(1), target :: ar
55+
type(c_ptr) :: val
56+
val = c_loc(ar)
57+
end function fun7

0 commit comments

Comments
 (0)