Skip to content

Commit 25e0f3d

Browse files
committed
Improves hover of functions
Functions now all display the same signature: `type` `keywords` `function` `name(args)` `result(val)` - Submodule module procedure functions now display like so - functions without an explicit type or result now display like so - Nested functions i.e. using functions as args displays like so Closes Add support for Fortran scope/block snippets #47 Adds a series of hover unittests. Also renamed the result variables
1 parent 3b71755 commit 25e0f3d

File tree

7 files changed

+102
-48
lines changed

7 files changed

+102
-48
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- Fixed hovering over functions displaying as theire result types
88
([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))
911

1012
## 2.2.1
1113

fortls/intrinsics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def create_object(json_obj, enc_obj=None):
117117
0,
118118
name,
119119
args=args,
120-
return_type=[json_obj["return"], keywords, keyword_info],
120+
result_type=[json_obj["return"], keywords, keyword_info],
121121
)
122122
elif json_obj["type"] == 3:
123123
return fortran_var(

fortls/objects.py

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,8 +1087,8 @@ def __init__(
10871087
args: str = "",
10881088
mod_flag: bool = False,
10891089
keywords: list = None,
1090-
return_type=None,
1091-
result_var=None,
1090+
result_type: list[str] = None, # TODO: make this a string
1091+
result_name: str = None,
10921092
):
10931093
super().__init__(file_ast, line_number, name, args, mod_flag, keywords)
10941094
self.args: str = args.replace(" ", "").lower()
@@ -1097,65 +1097,69 @@ def __init__(
10971097
self.in_children: list = []
10981098
self.missing_args: list = []
10991099
self.mod_scope: bool = mod_flag
1100-
self.result_var = result_var
1101-
self.result_obj = None
1102-
self.return_type = None
1103-
if return_type is not None:
1104-
self.return_type = return_type[0]
1100+
self.result_name: str = result_name
1101+
self.result_type: str = None
1102+
self.result_obj: fortran_var = None
1103+
if result_type:
1104+
self.result_type = result_type[0]
1105+
# Set the implicit result() name to be the function name
1106+
if self.result_name is None:
1107+
self.result_name = self.name
11051108

11061109
def copy_interface(self, copy_source: fortran_function):
11071110
# Call the parent class method
11081111
child_names = super().copy_interface(copy_source)
11091112
# Return specific options
1110-
self.result_var = copy_source.result_var
1113+
self.result_name = copy_source.result_name
1114+
self.result_type = copy_source.result_type
11111115
self.result_obj = copy_source.result_obj
11121116
if copy_source.result_obj is not None:
11131117
if copy_source.result_obj.name.lower() not in child_names:
11141118
self.in_children.append(copy_source.result_obj)
11151119

11161120
def resolve_link(self, obj_tree):
11171121
self.resolve_arg_link(obj_tree)
1118-
if self.result_var is not None:
1119-
result_var_lower = self.result_var.lower()
1120-
for child in self.children:
1121-
if child.name.lower() == result_var_lower:
1122-
self.result_obj = child
1122+
result_var_lower = self.result_name.lower()
1123+
for child in self.children:
1124+
if child.name.lower() == result_var_lower:
1125+
self.result_obj = child
1126+
# Update result value and type
1127+
self.result_name = child.name
1128+
self.result_type = child.get_desc()
11231129

11241130
def get_type(self, no_link=False):
11251131
return FUNCTION_TYPE_ID
11261132

11271133
def get_desc(self):
1128-
if self.result_obj is not None:
1129-
return self.result_obj.get_desc() + " FUNCTION"
1130-
if self.return_type is not None:
1131-
return self.return_type + " FUNCTION"
1134+
if self.result_type:
1135+
return self.result_type + " FUNCTION"
11321136
return "FUNCTION"
11331137

11341138
def is_callable(self):
11351139
return False
11361140

11371141
def get_hover(self, long=False, include_doc=True, drop_arg=-1):
11381142
fun_sig, _ = self.get_snippet(drop_arg=drop_arg)
1139-
fun_return = ""
1140-
if self.result_obj is not None:
1141-
fun_return, _ = self.result_obj.get_hover(include_doc=False)
1142-
if self.return_type is not None:
1143-
fun_return = self.return_type
1144-
keyword_list = get_keywords(self.keywords)
1143+
fun_sig += f" RESULT({self.result_name})"
1144+
keyword_list = []
1145+
if self.result_type:
1146+
keyword_list.append(self.result_type)
1147+
keyword_list += get_keywords(self.keywords)
11451148
keyword_list.append("FUNCTION")
1146-
hover_array = [f"{fun_return} {' '.join(keyword_list)} {fun_sig}"]
1149+
1150+
hover_array = [f"{' '.join(keyword_list)} {fun_sig}"]
11471151
self.get_docs_full(hover_array, long, include_doc, drop_arg)
11481152
return "\n ".join(hover_array), long
11491153

11501154
def get_interface(self, name_replace=None, change_arg=-1, change_strings=None):
11511155
fun_sig, _ = self.get_snippet(name_replace=name_replace)
1156+
fun_sig += f" RESULT({self.result_name})"
11521157
keyword_list = []
1153-
if self.return_type is not None:
1154-
keyword_list.append(self.return_type)
1155-
if self.result_obj is not None:
1156-
fun_sig += f" RESULT({self.result_obj.name})"
1158+
if self.result_type:
1159+
keyword_list.append(self.result_type)
11571160
keyword_list += get_keywords(self.keywords)
11581161
keyword_list.append("FUNCTION ")
1162+
11591163
interface_array = self.get_interface_array(
11601164
keyword_list, fun_sig, change_arg, change_strings
11611165
)

fortls/parse_fortran.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,23 @@ def read_var_def(line: str, type_word: str = None, fun_only: bool = False):
287287
return "var", VAR_info(type_word, keywords, var_words)
288288

289289

290-
def read_fun_def(line: str, return_type=None, mod_flag: bool = False):
291-
"""Attempt to read FUNCTION definition line"""
290+
def read_fun_def(line: str, result_type=None, mod_flag: bool = False):
291+
"""Attempt to read FUNCTION definition line
292+
293+
Parameters
294+
----------
295+
line : str
296+
file line
297+
result_type : str, optional
298+
type of function e.g. INTEGER, REAL, etc., by default None
299+
mod_flag : bool, optional
300+
flag for module and module procedure parsing, by default False
301+
302+
Returns
303+
-------
304+
tuple[Literal['fun'], FUN_info]
305+
a named tuple
306+
"""
292307
mod_match = SUB_MOD_REGEX.match(line)
293308
mods_found = False
294309
keywords: list[str] = []
@@ -317,14 +332,14 @@ def read_fun_def(line: str, return_type=None, mod_flag: bool = False):
317332
word_match = [word for word in word_match]
318333
args = ",".join(word_match)
319334
trailing_line = trailing_line[paren_match.end(0) :]
320-
#
321-
return_var = None
322-
if return_type is None:
323-
trailing_line = trailing_line.strip()
324-
results_match = RESULT_REGEX.match(trailing_line)
325-
if results_match is not None:
326-
return_var = results_match.group(1).strip().lower()
327-
return "fun", FUN_info(name, args, return_type, return_var, mod_flag, keywords)
335+
336+
# Extract if possible the variable name of the result()
337+
result_name = None
338+
trailing_line = trailing_line.strip()
339+
results_match = RESULT_REGEX.match(trailing_line)
340+
if results_match:
341+
result_name = results_match.group(1).strip().lower()
342+
return "fun", FUN_info(name, args, result_type, result_name, mod_flag, keywords)
328343

329344

330345
def read_sub_def(line: str, mod_flag: bool = False):
@@ -1772,16 +1787,21 @@ def parser_debug_msg(msg: str, line: str, ln: int):
17721787
args=obj_info.args,
17731788
mod_flag=obj_info.mod_flag,
17741789
keywords=keywords,
1775-
return_type=obj_info.return_type,
1776-
result_var=obj_info.return_var,
1790+
result_type=obj_info.return_type,
1791+
result_name=obj_info.return_var,
17771792
)
17781793
file_ast.add_scope(new_fun, END_FUN_REGEX)
1779-
if obj_info.return_type is not None:
1794+
# function type is present without result(), register the automatic
1795+
# result() variable that is the function name
1796+
if obj_info.return_type:
1797+
result_name = obj_info.name
1798+
if obj_info.return_var:
1799+
result_name = obj_info.return_var
17801800
keywords, keyword_info = map_keywords(obj_info.return_type[1])
17811801
new_obj = fortran_var(
17821802
file_ast,
17831803
line_number,
1784-
obj_info.name,
1804+
result_name,
17851805
obj_info.return_type[0],
17861806
keywords,
17871807
keyword_info,

test/test_server.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,12 @@ def check_return(result_array, checks):
576576
file_path = test_dir / "hover" / "functions.f90"
577577
string += hover_req(file_path, 1, 11)
578578
string += hover_req(file_path, 7, 19)
579+
string += hover_req(file_path, 12, 12)
580+
string += hover_req(file_path, 18, 19)
581+
file_path = test_dir / "subdir" / "test_submod.F90"
582+
string += hover_req(file_path, 29, 24)
583+
file_path = test_dir / "test_diagnostic_int.f90"
584+
string += hover_req(file_path, 19, 14)
579585

580586
errcode, results = run_request(
581587
string, fortls_args=["--variable_hover", "--sort_keywords"]
@@ -594,10 +600,21 @@ def check_return(result_array, checks):
594600
"DOUBLE PRECISION, PARAMETER :: somevar = 23.12",
595601
"DOUBLE PRECISION, PARAMETER :: some = 1e-19",
596602
"INTEGER, POINTER",
597-
"""FUNCTION fun1(arg)
603+
"""INTEGER FUNCTION fun1(arg) RESULT(fun1)
604+
INTEGER, INTENT(IN) :: arg""",
605+
"""INTEGER FUNCTION fun2(arg) RESULT(fun2)
606+
INTEGER, INTENT(IN) :: arg""",
607+
"""INTEGER FUNCTION fun3(arg) RESULT(retval)
598608
INTEGER, INTENT(IN) :: arg""",
599-
"""INTEGER FUNCTION fun2(arg)
609+
"""INTEGER FUNCTION fun4(arg) RESULT(retval)
600610
INTEGER, INTENT(IN) :: arg""",
611+
"""REAL FUNCTION point_dist(a, b) RESULT(distance)
612+
TYPE(point), INTENT(IN) :: a
613+
TYPE(point), INTENT(IN) :: b""",
614+
"""REAL FUNCTION foo2(f, g, h) RESULT(arg3)
615+
REAL FUNCTION f(x) RESULT(z) :: f
616+
REAL FUNCTION g(x) RESULT(z) :: g
617+
REAL FUNCTION h(x) RESULT(z) :: h""",
601618
)
602619
assert len(ref_results) == len(results) - 1
603620
check_return(results[1:], ref_results)

test/test_source/hover/functions.f90

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,14 @@ end function fun1
88
integer function fun2(arg)
99
integer, intent(in) :: arg
1010
end function fun2
11+
12+
! function with return
13+
function fun3(arg) result(retval)
14+
integer, intent(in) :: arg
15+
integer :: retval
16+
end function fun3
17+
18+
! function with type on definition and return
19+
integer function fun4(arg) result(retval)
20+
integer, intent(in) :: arg
21+
end function fun4

test/test_source/subdir/test_submod.F90

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ end module points
2727
#define __PARENT_MOD__ points
2828
submodule (__PARENT_MOD__) points_a
2929
contains
30-
module function point_dist
31-
type(point) :: c
30+
module function point_dist(a, b)
31+
type(point), intent(in) :: a, b
3232
distance = sqrt((a%x - b%x)**2 + (a%y - b%y)**2)
3333
end function point_dist
3434

@@ -40,5 +40,5 @@ end function point_dist
4040
module procedure is_point_equal_sub
4141
type(point) :: c
4242
test = is_point_equal(a,b)
43-
end module procedure is_point_equal_sub
43+
end procedure is_point_equal_sub
4444
end submodule points_a

0 commit comments

Comments
 (0)