Skip to content

Commit 3f7da8b

Browse files
authored
Merge pull request #388 from fortran-lang/refactor/small-improvements
refactor/small improvements
2 parents 9d582ba + 09e87a6 commit 3f7da8b

File tree

5 files changed

+91
-64
lines changed

5 files changed

+91
-64
lines changed

fortls/parsers/internal/ast.py

Lines changed: 60 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
class FortranAST:
2020
def __init__(self, file_obj=None):
2121
self.file = file_obj
22-
self.path: str = None
23-
if file_obj is not None:
24-
self.path = file_obj.path
22+
self.path: str | None = file_obj.path if file_obj is not None else None
2523
self.global_dict: dict = {}
2624
self.scope_list: list = []
2725
self.variable_list: list = []
@@ -39,10 +37,10 @@ def __init__(self, file_obj=None):
3937
self.none_scope = None
4038
self.inc_scope = None
4139
self.current_scope = None
42-
self.END_SCOPE_REGEX: Pattern = None
43-
self.enc_scope_name: str = None
40+
self.end_scope_regex: Pattern | None = None
41+
self.enc_scope_name: str | None = None
4442
self.last_obj = None
45-
self.pending_doc: str = None
43+
self.pending_doc: str | None = None
4644

4745
def create_none_scope(self):
4846
"""Create empty scope to hold non-module contained items"""
@@ -60,7 +58,7 @@ def get_enc_scope_name(self):
6058
def add_scope(
6159
self,
6260
new_scope: Scope,
63-
END_SCOPE_REGEX: Pattern[str],
61+
end_scope_regex: Pattern[str],
6462
exportable: bool = True,
6563
req_container: bool = False,
6664
):
@@ -80,10 +78,10 @@ def add_scope(
8078
else:
8179
self.current_scope.add_child(new_scope)
8280
self.scope_stack.append(self.current_scope)
83-
if self.END_SCOPE_REGEX is not None:
84-
self.end_stack.append(self.END_SCOPE_REGEX)
81+
if self.end_scope_regex is not None:
82+
self.end_stack.append(self.end_scope_regex)
8583
self.current_scope = new_scope
86-
self.END_SCOPE_REGEX = END_SCOPE_REGEX
84+
self.end_scope_regex = end_scope_regex
8785
self.enc_scope_name = self.get_enc_scope_name()
8886
self.last_obj = new_scope
8987
if self.pending_doc is not None:
@@ -102,9 +100,9 @@ def end_scope(self, line_number: int, check: bool = True):
102100
else:
103101
self.current_scope = None
104102
if len(self.end_stack) > 0:
105-
self.END_SCOPE_REGEX = self.end_stack.pop()
103+
self.end_scope_regex = self.end_stack.pop()
106104
else:
107-
self.END_SCOPE_REGEX = None
105+
self.end_scope_regex = None
108106
self.enc_scope_name = self.get_enc_scope_name()
109107

110108
def add_variable(self, new_var: Variable):
@@ -170,11 +168,11 @@ def add_error(self, msg: str, sev: int, ln: int, sch: int, ech: int = None):
170168
def start_ppif(self, line_number: int):
171169
self.pp_if.append([line_number - 1, -1])
172170

173-
def end_ppif(self, line_number):
171+
def end_ppif(self, line_number: int):
174172
if len(self.pp_if) > 0:
175173
self.pp_if[-1][1] = line_number - 1
176174

177-
def get_scopes(self, line_number: int = None):
175+
def get_scopes(self, line_number: int | None = None):
178176
"""Get a list of all the scopes present in the line number provided.
179177
180178
Parameters
@@ -191,65 +189,69 @@ def get_scopes(self, line_number: int = None):
191189
return self.scope_list
192190
scope_list = []
193191
for scope in self.scope_list:
194-
if (line_number >= scope.sline) and (line_number <= scope.eline):
195-
if type(scope.parent) is Interface:
196-
for use_stmnt in scope.use:
197-
if type(use_stmnt) is not Import:
198-
continue
199-
# Exclude the parent and all other scopes
200-
if use_stmnt.import_type == ImportTypes.NONE:
201-
return [scope]
202-
scope_list.append(scope)
203-
scope_list.extend(iter(scope.get_ancestors()))
192+
if not scope.sline <= line_number <= scope.eline:
193+
continue
194+
if type(scope.parent) is Interface:
195+
for use_stmnt in scope.use:
196+
if type(use_stmnt) is not Import:
197+
continue
198+
# Exclude the parent and all other scopes
199+
if use_stmnt.import_type == ImportTypes.NONE:
200+
return [scope]
201+
scope_list.append(scope)
202+
scope_list.extend(iter(scope.get_ancestors()))
204203
if scope_list or self.none_scope is None:
205204
return scope_list
206-
else:
207-
return [self.none_scope]
205+
return [self.none_scope]
208206

209207
def get_inner_scope(self, line_number: int):
210208
scope_sline = -1
211209
curr_scope = None
212210
for scope in self.scope_list:
213-
if scope.sline > scope_sline and (
214-
(line_number >= scope.sline) and (line_number <= scope.eline)
215-
):
211+
if scope.sline > scope_sline and scope.sline <= line_number <= scope.eline:
216212
curr_scope = scope
217213
scope_sline = scope.sline
218214
if (curr_scope is None) and (self.none_scope is not None):
219215
return self.none_scope
220216
return curr_scope
221217

222218
def get_object(self, FQSN: str):
223-
FQSN_split = FQSN.split("::")
224-
curr_obj = self.global_dict.get(FQSN_split[0])
225-
if curr_obj is None:
226-
# Look for non-exportable scopes
227-
for scope in self.scope_list:
228-
if FQSN_split[0] == scope.FQSN:
229-
curr_obj = scope
230-
break
231-
if curr_obj is None:
219+
def find_child_by_name(parent, name):
220+
for child in parent.children:
221+
if child.name == name:
222+
return child
223+
if child.name.startswith("#GEN_INT"):
224+
found = next(
225+
(
226+
int_child
227+
for int_child in child.get_children()
228+
if int_child.name == name
229+
),
230+
None,
231+
)
232+
if found:
233+
return found
232234
return None
233-
if len(FQSN_split) > 1:
234-
for name in FQSN_split[1:]:
235-
next_obj = None
236-
for child in curr_obj.children:
237-
if child.name.startswith("#GEN_INT"):
238-
for int_child in child.get_children():
239-
if int_child.name == name:
240-
next_obj = int_child
241-
break
242-
if next_obj is not None:
243-
break
244-
if child.name == name:
245-
next_obj = child
246-
break
247-
if next_obj is None:
248-
return None
249-
curr_obj = next_obj
250-
return curr_obj
251-
252-
def resolve_includes(self, workspace, path: str = None):
235+
236+
parts = FQSN.split("::")
237+
current = self.global_dict.get(parts[0])
238+
239+
# Look for non-exportable scopes
240+
if current is None:
241+
current = next(
242+
(scope for scope in self.scope_list if scope.FQSN == parts[0]), None
243+
)
244+
if current is None:
245+
return None
246+
247+
for part in parts[1:]:
248+
current = find_child_by_name(current, part)
249+
if current is None:
250+
return None
251+
252+
return current
253+
254+
def resolve_includes(self, workspace, path: str | None = None):
253255
file_dir = os.path.dirname(self.path)
254256
for inc in self.include_statements:
255257
file_path = os.path.normpath(os.path.join(file_dir, inc.path))

fortls/parsers/internal/parser.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from fortls.constants import (
1919
DO_TYPE_ID,
2020
INTERFACE_TYPE_ID,
21-
MODULE_TYPE_ID,
2221
SELECT_TYPE_ID,
2322
SUBMODULE_TYPE_ID,
2423
FRegex,
@@ -1352,7 +1351,7 @@ def parse(
13521351
line = multi_lines.pop()
13531352
line_stripped = line
13541353
# Test for scope end
1355-
if file_ast.END_SCOPE_REGEX is not None:
1354+
if file_ast.end_scope_regex is not None:
13561355
match = FRegex.END_WORD.match(line_no_comment)
13571356
# Handle end statement
13581357
if self.parse_end_scope_word(line_no_comment, line_no, file_ast, match):
@@ -1657,10 +1656,10 @@ def parse(
16571656
msg = "Visibility statement without enclosing scope"
16581657
file_ast.add_error(msg, Severity.error, line_no, 0)
16591658
else:
1660-
if (len(obj_info.obj_names) == 0) and (obj_info.type == 1):
1659+
if len(obj_info.obj_names) == 0 and obj_info.type == 1: # private
16611660
file_ast.current_scope.set_default_vis(-1)
16621661
else:
1663-
if obj_info.type == MODULE_TYPE_ID:
1662+
if obj_info.type == 1: # private
16641663
for word in obj_info.obj_names:
16651664
file_ast.add_private(word)
16661665
else:
@@ -1766,7 +1765,7 @@ def parse_end_scope_word(
17661765
):
17671766
file_ast.end_errors.append([ln, file_ast.current_scope.sline])
17681767
else:
1769-
scope_match = file_ast.END_SCOPE_REGEX.match(line[match.start(1) :])
1768+
scope_match = file_ast.end_scope_regex.match(line[match.start(1) :])
17701769
if scope_match is not None:
17711770
end_scope_word = scope_match.group(0)
17721771
if end_scope_word is not None:

test/test_parser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,11 @@ def test_submodule():
3131
except Exception as e:
3232
print(e)
3333
assert False
34+
35+
36+
def test_private_visibility_interfaces():
37+
file_path = test_dir / "vis" / "private.f90"
38+
file = FortranFile(str(file_path))
39+
err_str, _ = file.load_from_disk()
40+
file.parse()
41+
assert err_str is None

test/test_source/.fortls

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"./diag/",
1010
"docs",
1111
"rename",
12-
"parse"
12+
"parse",
13+
"vis"
1314
]
1415

1516
}

test/test_source/vis/private.f90

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module visibility
2+
private :: name
3+
private :: generic_interf
4+
interface name
5+
module procedure :: name_sp
6+
end interface name
7+
interface
8+
subroutine generic_interf(noop)
9+
integer, intent(in) :: noop
10+
end subroutine generic_interf
11+
end interface
12+
contains
13+
subroutine name_sp(val)
14+
real(4), intent(in) :: val
15+
print *, 'name_sp', val
16+
end subroutine name_sp
17+
end module visibility

0 commit comments

Comments
 (0)