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

Fix preprocessor else and elif #331

Merged
merged 10 commits into from
Oct 29, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

### Fixed

- Fixed preprocessor bug with `if` and `elif` conditionals
([#322](https://github.com/fortran-lang/fortls/issues/322))
- Fixed bug where type fields or methods were not detected if spaces were
used around `%`
([#286](https://github.com/fortran-lang/fortls/issues/286))
Expand Down
41 changes: 34 additions & 7 deletions fortls/parse_fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -2090,6 +2090,7 @@ def replace_vars(line: str):
pp_skips = []
pp_defines = []
pp_stack = []
pp_stack_group = []
defs_tmp = pp_defs.copy()
def_regexes = {}
output_file = []
Expand Down Expand Up @@ -2135,25 +2136,49 @@ def replace_vars(line: str):
# Closing/middle conditional statements
inc_start = False
exc_start = False
exc_continue = False
if match.group(1) == "elif":
if pp_stack[-1][0] < 0:
pp_stack[-1][0] = i + 1
exc_start = True
if (not pp_stack_group) or (pp_stack_group[-1][0] != len(pp_stack)):
# First elif statement for this elif group
if pp_stack[-1][0] < 0:
pp_stack_group.append([len(pp_stack), True])
else:
pp_stack_group.append([len(pp_stack), False])
if pp_stack_group[-1][1]:
# An earlier if or elif in this group has been true
exc_continue = True
if pp_stack[-1][0] < 0:
pp_stack[-1][0] = i + 1
elif eval_pp_if(line[match.end(1) :], defs_tmp):
pp_stack[-1][1] = i + 1
pp_skips.append(pp_stack.pop())
pp_stack_group[-1][1] = True
pp_stack.append([-1, -1])
inc_start = True
else:
if eval_pp_if(line[match.end(1) :], defs_tmp):
pp_stack[-1][1] = i - 1
pp_stack.append([-1, -1])
inc_start = True
exc_start = True
elif match.group(1) == "else":
if pp_stack[-1][0] < 0:
pp_stack[-1][0] = i + 1
exc_start = True
elif (
pp_stack_group
and (pp_stack_group[-1][0] == len(pp_stack))
and (pp_stack_group[-1][1])
):
# An earlier if or elif in this group has been true
exc_continue = True
else:
pp_stack[-1][1] = i + 1
pp_skips.append(pp_stack.pop())
pp_stack.append([-1, -1])
inc_start = True
elif match.group(1) == "endif":
if pp_stack_group and (pp_stack_group[-1][0] == len(pp_stack)):
pp_stack_group.pop()
if pp_stack[-1][0] < 0:
pp_stack.pop()
log.debug(f"{line.strip()} !!! Conditional TRUE/END({i + 1})")
continue
if pp_stack[-1][1] < 0:
pp_stack[-1][1] = i + 1
Expand All @@ -2164,6 +2189,8 @@ def replace_vars(line: str):
log.debug(f"{line.strip()} !!! Conditional TRUE({i + 1})")
elif exc_start:
log.debug(f"{line.strip()} !!! Conditional FALSE({i + 1})")
elif exc_continue:
log.debug(f"{line.strip()} !!! Conditional EXCLUDED({i + 1})")
continue
# Handle variable/macro definitions files
match = FRegex.PP_DEF.match(line)
Expand Down
19 changes: 19 additions & 0 deletions test/test_preproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ def check_return(result_array, checks):
string += hover_req(file_path, 10, 15) # defined without ()
file_path = root_dir / "preproc_keywords.F90"
string += hover_req(file_path, 6, 2) # ignores PP across Fortran line continuations
file_path = root_dir / "preproc_else.F90"
string += hover_req(file_path, 8, 12)
string += hover_req(file_path, 18, 12)
file_path = root_dir / "preproc_elif.F90"
string += hover_req(file_path, 22, 15)
string += hover_req(file_path, 24, 10)
file_path = root_dir / "preproc_elif_elif_skip.F90"
string += hover_req(file_path, 30, 23)
file_path = root_dir / "preproc_if_elif_else.F90"
string += hover_req(file_path, 30, 23)
file_path = root_dir / "preproc_if_elif_skip.F90"
string += hover_req(file_path, 30, 23)
config = str(root_dir / ".pp_conf.json")
errcode, results = run_request(string, ["--config", config])
assert errcode == 0
Expand All @@ -49,6 +61,13 @@ def check_return(result_array, checks):
),
"```fortran90\n#define SUCCESS .true.\n```",
"```fortran90\nREAL, CONTIGUOUS, POINTER, DIMENSION(:) :: var1\n```",
"```fortran90\nINTEGER :: var0\n```",
"```fortran90\nREAL :: var1\n```",
"```fortran90\nINTEGER :: var2\n```",
"```fortran90\nINTEGER, INTENT(INOUT) :: var\n```",
"```fortran90\nINTEGER, PARAMETER :: res = 0+1+0+0\n```",
"```fortran90\nINTEGER, PARAMETER :: res = 0+0+0+1\n```",
"```fortran90\nINTEGER, PARAMETER :: res = 1+0+0+0\n```",
)
assert len(ref_results) == len(results) - 1
check_return(results[1:], ref_results)
27 changes: 27 additions & 0 deletions test/test_source/pp/preproc_elif.F90
AljenU marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
subroutine preprocessor_elif(var, var3, var4, var5, var6)

! This file, as used in test_preproc, checks that
! 1. the steps after the preprocessor parsing has fully finished, are only
! using content from the parts within the preprocessor if-elif-else that
! should be used. To do this, it has some regular fortran code within the
! #if and #elif.
! 2. the #endif correctly concludes the if-elif, so any new #define statements
! that come after the #endif, are picked up during the preprocessor parsing.

#if 0
integer, intent(in) :: var
#elif 1
integer, intent(inout) :: var
var = 3
#else
integer, intent(out) :: var
var = 5
#endif

#define OTHERTYPE integer

OTHERTYPE :: var2

PRINT*, var

endsubroutine preprocessor_elif
33 changes: 33 additions & 0 deletions test/test_source/pp/preproc_elif_elif_skip.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
subroutine preprocessor_elif_elif_skip()

! This file, as used in test_preproc, and together with the two similar files,
! tests that when there is an if-elif-elif-else, only the first branch that
! evaluates to true is used, and the others ignored. Also when multiple
! conditions evaluate to true.

#if 0
#define PART1 0
#elif 1
#define PART2 1
#elif 1
#define PART3 0
#else
#define PART4 0
#endif

#ifndef PART1
#define PART1 0
#endif
#ifndef PART2
#define PART2 0
#endif
#ifndef PART3
#define PART3 0
#endif
#ifndef PART4
#define PART4 0
#endif

integer, parameter :: res = PART1+PART2+PART3+PART4

end subroutine preprocessor_elif_elif_skip
21 changes: 21 additions & 0 deletions test/test_source/pp/preproc_else.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
subroutine preprocessor_else(var)

#if 0
#define MYTYPE logical
#else
#define MYTYPE integer
#endif

MYTYPE :: var0

#undef MYTYPE

#if 1
#define MYTYPE real
#else
#define MYTYPE character
#endif

MYTYPE :: var1

endsubroutine preprocessor_else
33 changes: 33 additions & 0 deletions test/test_source/pp/preproc_if_elif_else.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
subroutine preprocessor_if_elif_else()

! This file, as used in test_preproc, and together with the two similar files,
! tests that when there is an if-elif-elif-else, only the first branch that
! evaluates to true is used, and the others ignored. Also when multiple
! conditions evaluate to true.

#if 0
#define PART1 0
#elif 0
#define PART2 0
#elif 0
#define PART3 0
#else
#define PART4 1
#endif

#ifndef PART1
#define PART1 0
#endif
#ifndef PART2
#define PART2 0
#endif
#ifndef PART3
#define PART3 0
#endif
#ifndef PART4
#define PART4 0
#endif

integer, parameter :: res = PART1+PART2+PART3+PART4

endsubroutine preprocessor_if_elif_else
33 changes: 33 additions & 0 deletions test/test_source/pp/preproc_if_elif_skip.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
subroutine preprocessor_if_elif_skip()

! This file, as used in test_preproc, and together with the two similar files,
! tests that when there is an if-elif-elif-else, only the first branch that
! evaluates to true is used, and the others ignored. Also when multiple
! conditions evaluate to true.

#if 1
#define PART1 1
#elif 0
#define PART2 0
#elif 1
#define PART3 0
#else
#define PART4 0
#endif

#ifndef PART1
#define PART1 0
#endif
#ifndef PART2
#define PART2 0
#endif
#ifndef PART3
#define PART3 0
#endif
#ifndef PART4
#define PART4 0
#endif

integer, parameter :: res = PART1+PART2+PART3+PART4

end subroutine preprocessor_if_elif_skip