Skip to content

Add Fortran/Fypp static analysis linter#1193

Draft
sbryngelson wants to merge 10 commits intoMFlowCode:masterfrom
sbryngelson:feat/source-linter
Draft

Add Fortran/Fypp static analysis linter#1193
sbryngelson wants to merge 10 commits intoMFlowCode:masterfrom
sbryngelson:feat/source-linter

Conversation

@sbryngelson
Copy link
Member

@sbryngelson sbryngelson commented Feb 21, 2026

Summary

Adds a new Python-based static analysis linter (toolchain/mfc/lint_source.py) that catches patterns known to cause silent bugs in Fortran/Fypp source code. Integrated into both the local precheck hook and CI.

Checks

Check What it catches Example
Fypp list duplicates Duplicate entries in #:for VAR in [...] lists bc_x%ve2 listed twice in MPI broadcast, silently skipping bc_x%ve3
Duplicate consecutive lines Identical adjacent non-trivial lines (≥40 chars) Doubled R3bar accumulation, repeated subroutine argument
Hardcoded byte size int(8._wp, ...) patterns that assume 8-byte reals WP_MOK = int(8._wp, MPI_OFFSET_KIND) breaks in single precision

All three checks are motivated by real bugs found in the codebase.

Integration

  • toolchain/bootstrap/precheck.sh: Added as step 5/6 (bumped doc check to 6/6), runs during ./mfc.sh precheck and the pre-commit hook
  • .github/workflows/lint-source.yml: Added as a new CI step alongside existing grep-based checks
  • CMakeLists.txt: Added -Wconversion to GNU debug builds, tracked in cleanliness CI

Companion Fortran fixes included

This PR also includes fixes for the 13 issues the linter finds on master today. Some of these fixes overlap with other open PRs:

These overlapping fixes are kept here because the linter requires a clean codebase to pass precheck. Whichever PR merges second will get a trivial merge conflict (identical changes on both sides).

The remaining fixes are unique to this PR:

  • bc_x%ve2bc_x%ve3 duplicate in src/simulation/m_mpi_proxy.fpp — MPI broadcast bug
  • dq_prim_dy_vfdq_prim_dz_vf in src/simulation/m_rhs.fpp — 3D cylindrical viscous stress bug

Test plan

  • Run python3 toolchain/mfc/lint_source.py — verify clean (no issues)
  • Run ./mfc.sh precheck — verify Fortran/Fypp analysis appears as step 5/6
  • Verify no false positives (all findings correspond to real bugs)

🤖 Generated with Claude Code

Closes #1211

Copilot AI review requested due to automatic review settings February 21, 2026 03:54
@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Feb 21, 2026
@codeant-ai
Copy link
Contributor

codeant-ai bot commented Feb 21, 2026

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • File I/O robustness
    The code calls Path.read_text(encoding="utf-8") without handling decoding errors. A non-UTF-8 file (or an unexpected byte sequence) will raise UnicodeDecodeError and crash the linter (causing precheck/CI to fail). Consider defensively handling read errors or falling back to a safe decoding strategy.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a Python-based static analysis linter (lint_source.py) to detect common Fortran/Fypp code issues that cause silent bugs. The linter checks for duplicate entries in Fypp loop lists, duplicate consecutive source lines, and hardcoded byte size assumptions that break single-precision builds.

Changes:

  • Added toolchain/mfc/lint_source.py with three static analysis checks for Fortran/Fypp code
  • Integrated the linter into the precheck hook as step 5/6
  • Added CI workflow step to run Fortran/Fypp static analysis

Reviewed changes

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

File Description
toolchain/mfc/lint_source.py New static analysis linter with checks for Fypp list duplicates, duplicate consecutive lines, and hardcoded byte sizes
toolchain/bootstrap/precheck.sh Updated step numbering to 6 total steps and added Fortran/Fypp analysis as step 5
.github/workflows/lint-source.yml Added new CI step to run the static analysis linter

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files

Confidence score: 5/5

  • Overall low risk since the only issue is a low-severity linting edge case, not a runtime or user-facing change.
  • Most notable concern: toolchain/mfc/lint_source.py only checks quoted list entries, so duplicate non-string items in #:for ... in [...] lists can slip through, reducing lint coverage.
  • Pay close attention to toolchain/mfc/lint_source.py - duplicate detection ignores non-string list items.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="toolchain/mfc/lint_source.py">

<violation number="1" location="toolchain/mfc/lint_source.py:62">
P2: This only checks quoted list entries, so duplicate numeric/tuple items in `#:for ... in [...]` lists (e.g., `[1, 2, 3]`) are silently ignored. The linter should parse the full list so duplicates in non-string entries are also detected.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
toolchain/mfc/lint_source.py (1)

42-43: _check_single_fypp_list — quote-matching regex allows mixed delimiters.

r"['\"]([^'\"]*)['\"]" matches any opening quote paired with any closing quote, so 'foo" is a valid match. In the MFC Fypp codebase, list entries are uniformly 'single-quoted', so no false positives have been seen. Consider using two separate patterns or a back-reference to enforce consistent quoting:

♻️ Proposed fix using alternation with back-reference
-    entries = re.findall(r"['\"]([^'\"]*)['\"]", list_content)
+    entries = re.findall(r"'([^']*)'|\"([^\"]*)\"", list_content)
+    entries = [a or b for a, b in entries]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@toolchain/mfc/lint_source.py` around lines 42 - 43, The regex in
_check_single_fypp_list that builds entries using re.findall currently allows
mismatched opening/closing quotes (e.g., "'foo\""); update the pattern used to
extract list_content entries to enforce matching delimiters — either restrict to
single quotes (since Fypp uses single-quoted entries) or use a
back-reference/alternation so the opening quote is the same as the closing
quote; modify the entries assignment (the variable entries derived from
list_content in _check_single_fypp_list) to use the corrected regex and ensure
subsequent logic that iterates over entries continues to work with the new
extraction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@toolchain/mfc/lint_source.py`:
- Around line 126-152: The check_hardcoded_byte_size function is flagging
patterns inside inline Fortran comments because byte_re is run against the whole
stripped line; to fix, before testing byte_re.search, remove the inline comment
portion by taking the substring up to the first unquoted '!' (i.e., split on '!'
and use the left part) so only actual code is scanned; update the loop in
check_hardcoded_byte_size to compute a code_only variable from line (respecting
full-line comments via _is_comment_or_blank) and run byte_re.search on that
code_only string (references: check_hardcoded_byte_size, _is_comment_or_blank,
byte_re, lines loop).

---

Nitpick comments:
In `@toolchain/mfc/lint_source.py`:
- Around line 42-43: The regex in _check_single_fypp_list that builds entries
using re.findall currently allows mismatched opening/closing quotes (e.g.,
"'foo\""); update the pattern used to extract list_content entries to enforce
matching delimiters — either restrict to single quotes (since Fypp uses
single-quoted entries) or use a back-reference/alternation so the opening quote
is the same as the closing quote; modify the entries assignment (the variable
entries derived from list_content in _check_single_fypp_list) to use the
corrected regex and ensure subsequent logic that iterates over entries continues
to work with the new extraction.

@codecov
Copy link

codecov bot commented Feb 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.05%. Comparing base (df28255) to head (6f3c845).
⚠️ Report is 29 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1193   +/-   ##
=======================================
  Coverage   44.05%   44.05%           
=======================================
  Files          70       70           
  Lines       20496    20495    -1     
  Branches     1989     1990    +1     
=======================================
  Hits         9030     9030           
+ Misses      10328    10327    -1     
  Partials     1138     1138           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codeant-ai codeant-ai bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Feb 21, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="toolchain/mfc/lint_source.py">

<violation number="1" location="toolchain/mfc/lint_source.py:145">
P2: The inline comment stripping uses `split("!")`, which also cuts `!` inside string literals. That can hide real `int(8._wp, ...)` usages on lines that include a string with `!`, leading to false negatives. Use a comment-stripping approach that ignores `!` inside quotes.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@sbryngelson sbryngelson marked this pull request as draft February 22, 2026 14:53
@MFlowCode MFlowCode deleted a comment from github-actions bot Feb 23, 2026
@sbryngelson sbryngelson force-pushed the feat/source-linter branch 2 times, most recently from d761cad to 6bd15c4 Compare February 24, 2026 16:03
sbryngelson and others added 5 commits February 24, 2026 11:49
Adds toolchain/mfc/lint_source.py with three checks that catch
copy-paste and precision bugs in Fortran/Fypp source:

- Duplicate entries in Fypp #:for lists (e.g. broadcast lists)
- Identical adjacent source lines (duplicated accumulations, args)
- Hardcoded int(8._wp, ...) that assumes 8-byte reals

Integrated into precheck.sh (step 5/6) and lint-source.yml CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds -Wconversion to the GNU debug compiler flags in CMakeLists.txt.
This catches implicit type coercions (e.g. integer = real truncation)
at compile time.

Adds a Conversion Warnings Diff step and count to cleanliness.yml,
following the same delta-from-master pattern as the existing checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract inner parsing logic of check_fypp_list_duplicates into
_check_single_fypp_list helper to resolve R0914 (too-many-locals),
R1702 (too-many-nested-blocks), and R1716 (chained-comparison).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
byte_re was run against the full stripped line, so a comment like
  ! old code: int(8._wp, kind=i64)
would be flagged even though the pattern is inside a Fortran ! comment.
Strip the inline comment portion before searching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This PR adds a 6th lint check (Fortran/Fypp analysis). Update
CLAUDE.md and common-pitfalls.md to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sbryngelson and others added 4 commits February 24, 2026 18:37
- m_mpi_proxy.fpp: fix duplicate 'bc_x%ve2' → 'bc_x%ve3' in MPI
  broadcast Fypp list; bc_x%ve3 was never broadcast to all ranks
- m_assign_variables.fpp: remove duplicate R3bar accumulation line
  in QBMM path; bubble radius cubed was summed twice per iteration
- m_rhs.fpp: fix cylindrical viscous boundary call passing
  dq_prim_dy_vf twice; 4th arg should be dq_prim_dz_vf (z-gradient)
- m_data_input.f90, m_data_output.fpp, m_start_up.fpp (all targets):
  replace hardcoded WP_MOK = int(8._wp,...) with storage_size(0._stp)/8
  for correct mixed-precision MPI-IO offset computation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MFlowCode MFlowCode deleted a comment from codeant-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from codeant-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from codeant-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from codeant-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from coderabbitai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from cubic-dev-ai bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from cubic-dev-ai bot Feb 26, 2026
The hardcoded byte-size linter should recommend storage_size(0._stp)/8,
not 0._wp, since field I/O uses storage precision. In mixed-precision
builds (wp=double, stp=half) the current message would misdirect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MFlowCode MFlowCode deleted a comment from github-actions bot Feb 26, 2026
@MFlowCode MFlowCode deleted a comment from github-actions bot Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Development

Successfully merging this pull request may close these issues.

Add Fortran/Fypp static analysis linter to catch copy-paste bugs

2 participants