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

refactor/small improvements #388

Merged
merged 7 commits into from
May 7, 2024
Merged
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
118 changes: 60 additions & 58 deletions fortls/parsers/internal/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
class FortranAST:
def __init__(self, file_obj=None):
self.file = file_obj
self.path: str = None
if file_obj is not None:
self.path = file_obj.path
self.path: str | None = file_obj.path if file_obj is not None else None
self.global_dict: dict = {}
self.scope_list: list = []
self.variable_list: list = []
Expand All @@ -39,10 +37,10 @@
self.none_scope = None
self.inc_scope = None
self.current_scope = None
self.END_SCOPE_REGEX: Pattern = None
self.enc_scope_name: str = None
self.end_scope_regex: Pattern | None = None
self.enc_scope_name: str | None = None
self.last_obj = None
self.pending_doc: str = None
self.pending_doc: str | None = None

def create_none_scope(self):
"""Create empty scope to hold non-module contained items"""
Expand All @@ -60,7 +58,7 @@
def add_scope(
self,
new_scope: Scope,
END_SCOPE_REGEX: Pattern[str],
end_scope_regex: Pattern[str],
exportable: bool = True,
req_container: bool = False,
):
Expand All @@ -80,10 +78,10 @@
else:
self.current_scope.add_child(new_scope)
self.scope_stack.append(self.current_scope)
if self.END_SCOPE_REGEX is not None:
self.end_stack.append(self.END_SCOPE_REGEX)
if self.end_scope_regex is not None:
self.end_stack.append(self.end_scope_regex)
self.current_scope = new_scope
self.END_SCOPE_REGEX = END_SCOPE_REGEX
self.end_scope_regex = end_scope_regex
self.enc_scope_name = self.get_enc_scope_name()
self.last_obj = new_scope
if self.pending_doc is not None:
Expand All @@ -102,9 +100,9 @@
else:
self.current_scope = None
if len(self.end_stack) > 0:
self.END_SCOPE_REGEX = self.end_stack.pop()
self.end_scope_regex = self.end_stack.pop()
else:
self.END_SCOPE_REGEX = None
self.end_scope_regex = None
self.enc_scope_name = self.get_enc_scope_name()

def add_variable(self, new_var: Variable):
Expand Down Expand Up @@ -170,11 +168,11 @@
def start_ppif(self, line_number: int):
self.pp_if.append([line_number - 1, -1])

def end_ppif(self, line_number):
def end_ppif(self, line_number: int):
if len(self.pp_if) > 0:
self.pp_if[-1][1] = line_number - 1

def get_scopes(self, line_number: int = None):
def get_scopes(self, line_number: int | None = None):
"""Get a list of all the scopes present in the line number provided.

Parameters
Expand All @@ -191,65 +189,69 @@
return self.scope_list
scope_list = []
for scope in self.scope_list:
if (line_number >= scope.sline) and (line_number <= scope.eline):
if type(scope.parent) is Interface:
for use_stmnt in scope.use:
if type(use_stmnt) is not Import:
continue
# Exclude the parent and all other scopes
if use_stmnt.import_type == ImportTypes.NONE:
return [scope]
scope_list.append(scope)
scope_list.extend(iter(scope.get_ancestors()))
if not scope.sline <= line_number <= scope.eline:
continue
if type(scope.parent) is Interface:
for use_stmnt in scope.use:
if type(use_stmnt) is not Import:
continue

Check warning on line 197 in fortls/parsers/internal/ast.py

View check run for this annotation

Codecov / codecov/patch

fortls/parsers/internal/ast.py#L197

Added line #L197 was not covered by tests
# Exclude the parent and all other scopes
if use_stmnt.import_type == ImportTypes.NONE:
return [scope]
scope_list.append(scope)
scope_list.extend(iter(scope.get_ancestors()))
if scope_list or self.none_scope is None:
return scope_list
else:
return [self.none_scope]
return [self.none_scope]

def get_inner_scope(self, line_number: int):
scope_sline = -1
curr_scope = None
for scope in self.scope_list:
if scope.sline > scope_sline and (
(line_number >= scope.sline) and (line_number <= scope.eline)
):
if scope.sline > scope_sline and scope.sline <= line_number <= scope.eline:
curr_scope = scope
scope_sline = scope.sline
if (curr_scope is None) and (self.none_scope is not None):
return self.none_scope
return curr_scope

def get_object(self, FQSN: str):
FQSN_split = FQSN.split("::")
curr_obj = self.global_dict.get(FQSN_split[0])
if curr_obj is None:
# Look for non-exportable scopes
for scope in self.scope_list:
if FQSN_split[0] == scope.FQSN:
curr_obj = scope
break
if curr_obj is None:
def find_child_by_name(parent, name):
for child in parent.children:
if child.name == name:
return child
if child.name.startswith("#GEN_INT"):
found = next(
(
int_child
for int_child in child.get_children()
if int_child.name == name
),
None,
)
if found:
return found
return None
if len(FQSN_split) > 1:
for name in FQSN_split[1:]:
next_obj = None
for child in curr_obj.children:
if child.name.startswith("#GEN_INT"):
for int_child in child.get_children():
if int_child.name == name:
next_obj = int_child
break
if next_obj is not None:
break
if child.name == name:
next_obj = child
break
if next_obj is None:
return None
curr_obj = next_obj
return curr_obj

def resolve_includes(self, workspace, path: str = None):

parts = FQSN.split("::")
current = self.global_dict.get(parts[0])

# Look for non-exportable scopes
if current is None:
current = next(

Check warning on line 241 in fortls/parsers/internal/ast.py

View check run for this annotation

Codecov / codecov/patch

fortls/parsers/internal/ast.py#L241

Added line #L241 was not covered by tests
(scope for scope in self.scope_list if scope.FQSN == parts[0]), None
)
if current is None:
return None

Check warning on line 245 in fortls/parsers/internal/ast.py

View check run for this annotation

Codecov / codecov/patch

fortls/parsers/internal/ast.py#L244-L245

Added lines #L244 - L245 were not covered by tests

for part in parts[1:]:
current = find_child_by_name(current, part)
if current is None:
return None

Check warning on line 250 in fortls/parsers/internal/ast.py

View check run for this annotation

Codecov / codecov/patch

fortls/parsers/internal/ast.py#L250

Added line #L250 was not covered by tests

return current

def resolve_includes(self, workspace, path: str | None = None):
file_dir = os.path.dirname(self.path)
for inc in self.include_statements:
file_path = os.path.normpath(os.path.join(file_dir, inc.path))
Expand Down
9 changes: 4 additions & 5 deletions fortls/parsers/internal/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from fortls.constants import (
DO_TYPE_ID,
INTERFACE_TYPE_ID,
MODULE_TYPE_ID,
SELECT_TYPE_ID,
SUBMODULE_TYPE_ID,
FRegex,
Expand Down Expand Up @@ -1352,7 +1351,7 @@ def parse(
line = multi_lines.pop()
line_stripped = line
# Test for scope end
if file_ast.END_SCOPE_REGEX is not None:
if file_ast.end_scope_regex is not None:
match = FRegex.END_WORD.match(line_no_comment)
# Handle end statement
if self.parse_end_scope_word(line_no_comment, line_no, file_ast, match):
Expand Down Expand Up @@ -1657,10 +1656,10 @@ def parse(
msg = "Visibility statement without enclosing scope"
file_ast.add_error(msg, Severity.error, line_no, 0)
else:
if (len(obj_info.obj_names) == 0) and (obj_info.type == 1):
if len(obj_info.obj_names) == 0 and obj_info.type == 1: # private
file_ast.current_scope.set_default_vis(-1)
else:
if obj_info.type == MODULE_TYPE_ID:
if obj_info.type == 1: # private
for word in obj_info.obj_names:
file_ast.add_private(word)
else:
Expand Down Expand Up @@ -1766,7 +1765,7 @@ def parse_end_scope_word(
):
file_ast.end_errors.append([ln, file_ast.current_scope.sline])
else:
scope_match = file_ast.END_SCOPE_REGEX.match(line[match.start(1) :])
scope_match = file_ast.end_scope_regex.match(line[match.start(1) :])
if scope_match is not None:
end_scope_word = scope_match.group(0)
if end_scope_word is not None:
Expand Down
8 changes: 8 additions & 0 deletions test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ def test_submodule():
except Exception as e:
print(e)
assert False


def test_private_visibility_interfaces():
file_path = test_dir / "vis" / "private.f90"
file = FortranFile(str(file_path))
err_str, _ = file.load_from_disk()
file.parse()
assert err_str is None
3 changes: 2 additions & 1 deletion test/test_source/.fortls
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"./diag/",
"docs",
"rename",
"parse"
"parse",
"vis"
]

}
17 changes: 17 additions & 0 deletions test/test_source/vis/private.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module visibility
private :: name
private :: generic_interf
interface name
module procedure :: name_sp
end interface name
interface
subroutine generic_interf(noop)
integer, intent(in) :: noop
end subroutine generic_interf
end interface
contains
subroutine name_sp(val)
real(4), intent(in) :: val
print *, 'name_sp', val
end subroutine name_sp
end module visibility
Loading