Skip to content

Fix crash on function-like macros with no arguments - #537

Open
kunalKumar-13 wants to merge 2 commits into
fortran-lang:masterfrom
kunalKumar-13:fix/macro-parsing
Open

Fix crash on function-like macros with no arguments#537
kunalKumar-13 wants to merge 2 commits into
fortran-lang:masterfrom
kunalKumar-13:fix/macro-parsing

Conversation

@kunalKumar-13

Copy link
Copy Markdown

Problem

A file containing a function-like macro with no arguments makes fortls abort while preprocessing:

re.PatternError: invalid group reference 10

The file, and every module it defines, then silently fails to parse — which is why #486 reports that go-to-definition, hover and completion all stop working, including for code outside the module.

Minimal reproduction from the issue:

#define ok() if(ie/=0) then; return; end if;

module A
    implicit none
contains
    subroutine B
    integer :: ie
    ie = 1
    ok()
    end subroutine
end module

The reporter noticed that #define ok ... works while #define ok() ... does not, which is the giveaway: the object-like and function-like paths differ.

Cause

expand_func_macro splits the argument list with def_args.split(","). For a zero-argument macro the argument list is the empty string, and "".split(",") returns [""], not [] — so the macro is treated as having one unnamed argument.

The substitution loop then runs:

re.sub(r"\b()\b", r"\1", sub)

That pattern matches the empty string at every word boundary, so group references get injected all through the replacement text. In the reported macro body one lands immediately before a digit in ie/=0, producing the literal \10 — which re reads as a reference to group 10 when the text is later used as a substitution template. Hence the error, and hence why the value 10 looks arbitrary.

Solution

Drop empty names before the argument count is taken. A zero-argument macro then compiles to \bok\s*\(\) and no argument substitution runs over its body.

Argument names are also passed through re.escape, which they were not before.

Testing

Two tests added to test/test_preproc_parser.py:

  • test_pp_zero_argument_function_macro — the issue's macro expands to its body instead of raising
  • test_pp_function_macro_arities — zero, one and two argument macros all expand correctly, so the fix does not regress the argument path

Both fail without the change. Full suite: 183 passed. pre-commit (flake8, black, isort, pyupgrade) clean.

One note: the two-argument expectation asserts ((1) + ( 2)) with the leading space intact. Argument capture does not strip whitespace, which is pre-existing behaviour and left alone so this change stays about the crash.

Fixes #486

A source file containing `#define ok() ...` made fortls abort with

    re.PatternError: invalid group reference 10

and the file, along with every module it defines, silently failed to parse.

`expand_func_macro` splits the argument list with `def_args.split(",")`.
For a zero-argument macro the argument list is the empty string, and
`"".split(",")` returns `[""]` rather than `[]`, so the macro was treated as
having a single unnamed argument. The substitution loop then ran

    re.sub(r"\b()\b", r"\1", sub)

whose pattern matches the empty string at every word boundary, so group
references were injected throughout the replacement text. In the reported
macro body `if(ie/=0) then; return; end if;` the reference landed immediately
before a digit, producing the literal `\10`, which `re` reads as a reference
to group 10 when the text is used as a substitution template.

Empty names are now dropped before the argument count is taken, so a
zero-argument macro compiles to `\bok\s*\(\)` and no substitution runs over
its body. Argument names are also escaped, which they were not before.

Adds tests for zero-, one- and two-argument macros.

Fixes fortran-lang#486
@kunalKumar-13
kunalKumar-13 requested a review from gnikit as a code owner August 26, 2026 07:43
Copilot AI lite review requested due to automatic review settings August 26, 2026 07:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a fortls preprocessor crash triggered by zero-argument function-like macros (e.g., #define ok() ...) by correctly treating empty argument lists as arity 0 and safely escaping argument names during substitution.

Changes:

  • Filter out empty argument names when expanding function-like macros to prevent erroneous substitution on () and subsequent re group-reference failures.
  • Escape macro argument names with re.escape before building substitution patterns.
  • Add regression tests covering zero-, one-, and two-argument macro expansion; document the fix in the changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
fortls/parsers/internal/parser.py Fixes macro argument parsing/substitution for zero-argument function-like macros and escapes argument names.
test/test_preproc_parser.py Adds regression tests for zero-argument macros and arity handling.
CHANGELOG.md Notes the crash fix and links to the reported issue.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +44 to +48
Regression test for #486. `"".split(",")` is `[""]`, not `[]`, so a macro
such as `ok()` was treated as having one unnamed argument. The argument
substitution then ran `\b()\b`, which matches at every word boundary, and
injected group references throughout the replacement text -- `ie/=0` became
`...\10`, and `re` raised "invalid group reference 10".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the docstring was not raw, so it held a literal backspace instead of the text. Fixed in 17950af.

The docstring quotes the offending regex and the \10 group reference. In a
normal string literal Python parses those as escape sequences, so the
docstring held control characters rather than the text it was meant to show.
@kunalKumar-13

Copy link
Copy Markdown
Author

The red X here is not this change. All 8 failing jobs fail at the same single step, Upload coverage to Codecov; Lint and Unittests pass in every one of them. It has been happening to every PR in the repo since about mid-July, including the project's own pre-commit-ci branch — I wrote it up with the evidence in #539.

I re-ran the CI sequence locally against this branch on Python 3.13 to be sure the change itself is clean:

black --check .                      ->  61 files unchanged
python -m fortls.schema; git diff    ->  no drift
pytest --doctest-modules -n auto     ->  196 passed

Nothing needed from me here as far as I can tell, but happy to rebase if that would help.

@kunalKumar-13

Copy link
Copy Markdown
Author

The red builds here are not from this change. On every failing job the only failing step is Upload coverage to CodecovSetup, Lint and Unittests all pass (196 passed), and the step fails on the action's own signature check:

gpg: Signature made Thu Jul  9 01:37:57 2026 UTC
gpg:                using RSA key 27034E7FDB850E0BBC2C62FF806BB28AED779869
gpg: Can't check signature: No public key
==> Could not verify signature. Please contact Codecov if problem continues
    Exiting...

It is repo-wide rather than specific to this PR: the most recent Tests run on master fails on the same step in the same job, and every push-triggered run since 2026-07-13 has failed the same way. #533 bumps codecov/codecov-action and may be the remedy, though its own checks predate the breakage so I can't say that from the runs alone.

Happy to rebase once CI is healthy if that would help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Macros break fortls.

2 participants