Summary
language_standards.py's fortran func_start regex:
^[ \t]*(?!\bEND\b)(?:(?:PURE|ELEMENTAL|RECURSIVE|IMPURE|MODULE)[ \t\n]+){0,5}(?:(?:INTEGER|REAL|COMPLEX|LOGICAL|CHARACTER|TYPE|CLASS|DOUBLE[ \t\n]+PRECISION|DOUBLE[ \t\n]+COMPLEX)[A-Za-z0-9_ \t\n&*,()=:]{0,40}?)?(?:FUNCTION|SUBROUTINE|PROGRAM|ENTRY)[ \t\n&+]+([A-Za-z_]\w*)(?=[ \t\n]*(?:[\(!&]|$|\bRESULT\b|\bBIND\b))
has an optional return-TYPE-prefix group whose character class includes \n and allows up to 40 characters ([A-Za-z0-9_ \t\n&*,()=:]{0,40}?) between the TYPE keyword and the following FUNCTION/SUBROUTINE/etc. keyword. This is meant to match a real return-type annotation directly preceding a FUNCTION declaration (INTEGER FUNCTION foo(x)), but nothing anchors it to being part of the same statement -- if an unrelated, already-terminated statement happens to start with one of the TYPE keywords and sits within ~40 characters (including blank lines) of a later, completely unrelated SUBROUTINE/FUNCTION declaration, the regex bridges across both to produce ONE match starting at the earlier, unrelated statement.
Confirmed repro
language-crucible/data/fortran/wrf/module_initialize_real.F, real source:
REAL , SAVE :: p_top_save
INTEGER :: internal_time_loop
SUBROUTINE init_domain ( grid )
func_start.finditer() on this file produces ONE match whose match.start() lands at INTEGER :: internal_time_loop (NOT at SUBROUTINE), with match.group(0) = ' INTEGER :: internal_time_loop\n\n\n\n\n\n SUBROUTINE init_domain' -- the regex treated the complete, already-terminated INTEGER :: internal_time_loop declaration as if it were an in-progress return-type prefix for init_domain, spanning 4 blank lines to get there. This is doubly wrong:
SUBROUTINE never has a return type in real Fortran syntax at all (only FUNCTION does) -- the TYPE-prefix group should never be reachable before a bare SUBROUTINE/PROGRAM/ENTRY keyword in the first place.
- Even for a real
FUNCTION, the TYPE-prefix should never be allowed to span across a separately-terminated statement (a real Fortran statement boundary -- end of line with no continuation &, or another declaration keyword) -- it should only match text that's genuinely part of the SAME, single return-type annotation immediately preceding the function keyword.
Impact
match.start() being wrong corrupts anything anchored to it: reported start_line, computed loc/body span (Mode A's greedy-to-next-match body starts from this wrong position), and any bounded-search logic anchored to the match's own start (confirmed via #1973's own Mode A args-search-window fix, which exposed this: the old unbounded whole-block args search accidentally still found the real signature further into an inflated span, masking this bug; a correctly bounded search anchored at the same wrong start_idx cannot reach it, surfacing a real regression that traces back to THIS bug, not the bounding logic itself).
Suggested fix
Two options, not mutually exclusive:
- Restrict the TYPE-prefix group to only apply before
FUNCTION (never SUBROUTINE/PROGRAM/ENTRY) -- mirrors real Fortran grammar exactly.
- Tighten the type-prefix's character class so it can't cross a real statement boundary -- e.g. disallow more than one blank line, or require the prefix to not contain a line that looks like its own complete, differently-shaped declaration (a
:: double-colon variable declarator, which INTEGER :: internal_time_loop has and a real return-type annotation like INTEGER FUNCTION foo never does).
Needs the full Differential Scan verification chain (extraction gauntlet + strict tests, crucible_check.py against the full corpus, both golden masters re-blessed) since it's a language_standards.py change.
Summary
language_standards.py's fortranfunc_startregex:has an optional return-TYPE-prefix group whose character class includes
\nand allows up to 40 characters ([A-Za-z0-9_ \t\n&*,()=:]{0,40}?) between the TYPE keyword and the followingFUNCTION/SUBROUTINE/etc. keyword. This is meant to match a real return-type annotation directly preceding aFUNCTIONdeclaration (INTEGER FUNCTION foo(x)), but nothing anchors it to being part of the same statement -- if an unrelated, already-terminated statement happens to start with one of the TYPE keywords and sits within ~40 characters (including blank lines) of a later, completely unrelatedSUBROUTINE/FUNCTIONdeclaration, the regex bridges across both to produce ONE match starting at the earlier, unrelated statement.Confirmed repro
language-crucible/data/fortran/wrf/module_initialize_real.F, real source:func_start.finditer()on this file produces ONE match whosematch.start()lands atINTEGER :: internal_time_loop(NOT atSUBROUTINE), withmatch.group(0)=' INTEGER :: internal_time_loop\n\n\n\n\n\n SUBROUTINE init_domain'-- the regex treated the complete, already-terminatedINTEGER :: internal_time_loopdeclaration as if it were an in-progress return-type prefix forinit_domain, spanning 4 blank lines to get there. This is doubly wrong:SUBROUTINEnever has a return type in real Fortran syntax at all (onlyFUNCTIONdoes) -- the TYPE-prefix group should never be reachable before a bareSUBROUTINE/PROGRAM/ENTRYkeyword in the first place.FUNCTION, the TYPE-prefix should never be allowed to span across a separately-terminated statement (a real Fortran statement boundary -- end of line with no continuation&, or another declaration keyword) -- it should only match text that's genuinely part of the SAME, single return-type annotation immediately preceding the function keyword.Impact
match.start()being wrong corrupts anything anchored to it: reportedstart_line, computedloc/body span (Mode A's greedy-to-next-match body starts from this wrong position), and any bounded-search logic anchored to the match's own start (confirmed via #1973's own Mode A args-search-window fix, which exposed this: the old unbounded whole-block args search accidentally still found the real signature further into an inflated span, masking this bug; a correctly bounded search anchored at the same wrongstart_idxcannot reach it, surfacing a real regression that traces back to THIS bug, not the bounding logic itself).Suggested fix
Two options, not mutually exclusive:
FUNCTION(neverSUBROUTINE/PROGRAM/ENTRY) -- mirrors real Fortran grammar exactly.::double-colon variable declarator, whichINTEGER :: internal_time_loophas and a real return-type annotation likeINTEGER FUNCTION foonever does).Needs the full Differential Scan verification chain (extraction gauntlet + strict tests,
crucible_check.pyagainst the full corpus, both golden masters re-blessed) since it's alanguage_standards.pychange.