dimfort 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. dimfort-0.1.0/.github/workflows/release.yml +100 -0
  2. dimfort-0.1.0/.gitignore +243 -0
  3. dimfort-0.1.0/CHANGELOG.md +92 -0
  4. dimfort-0.1.0/CONTRIBUTING.md +89 -0
  5. dimfort-0.1.0/LICENSE +21 -0
  6. dimfort-0.1.0/PKG-INFO +164 -0
  7. dimfort-0.1.0/README.md +113 -0
  8. dimfort-0.1.0/docs/annotations.md +226 -0
  9. dimfort-0.1.0/docs/index.md +10 -0
  10. dimfort-0.1.0/docs/lsp.md +92 -0
  11. dimfort-0.1.0/docs/release.md +57 -0
  12. dimfort-0.1.0/docs/usage.md +99 -0
  13. dimfort-0.1.0/pyproject.toml +63 -0
  14. dimfort-0.1.0/scripts/make_branding.py +230 -0
  15. dimfort-0.1.0/social_preview.png +0 -0
  16. dimfort-0.1.0/src/dimfort/__init__.py +1 -0
  17. dimfort-0.1.0/src/dimfort/__main__.py +4 -0
  18. dimfort-0.1.0/src/dimfort/cli.py +224 -0
  19. dimfort-0.1.0/src/dimfort/config.py +162 -0
  20. dimfort-0.1.0/src/dimfort/core/__init__.py +6 -0
  21. dimfort-0.1.0/src/dimfort/core/_source_io.py +62 -0
  22. dimfort-0.1.0/src/dimfort/core/annotations.py +426 -0
  23. dimfort-0.1.0/src/dimfort/core/attach.py +240 -0
  24. dimfort-0.1.0/src/dimfort/core/default_units.toml +42 -0
  25. dimfort-0.1.0/src/dimfort/core/diagnostics.py +26 -0
  26. dimfort-0.1.0/src/dimfort/core/multifile.py +563 -0
  27. dimfort-0.1.0/src/dimfort/core/symbols.py +225 -0
  28. dimfort-0.1.0/src/dimfort/core/ts_checker.py +1468 -0
  29. dimfort-0.1.0/src/dimfort/core/ts_parser.py +385 -0
  30. dimfort-0.1.0/src/dimfort/core/unit_config.py +175 -0
  31. dimfort-0.1.0/src/dimfort/core/units.py +292 -0
  32. dimfort-0.1.0/src/dimfort/core/workspace_index.py +553 -0
  33. dimfort-0.1.0/src/dimfort/lsp/__init__.py +0 -0
  34. dimfort-0.1.0/src/dimfort/lsp/handlers.py +1 -0
  35. dimfort-0.1.0/src/dimfort/lsp/server.py +1967 -0
  36. dimfort-0.1.0/src/dimfort/lsp/ts_helpers.py +304 -0
  37. dimfort-0.1.0/tests/__init__.py +0 -0
  38. dimfort-0.1.0/tests/fixtures/hello.f90 +4 -0
  39. dimfort-0.1.0/tests/fixtures/multifile/geo.f90 +19 -0
  40. dimfort-0.1.0/tests/fixtures/multifile/main.f90 +25 -0
  41. dimfort-0.1.0/tests/fixtures/multifile_scope/file_a.f90 +12 -0
  42. dimfort-0.1.0/tests/fixtures/multifile_scope/file_b.f90 +13 -0
  43. dimfort-0.1.0/tests/fixtures/smoke_ast_phase1.f90 +43 -0
  44. dimfort-0.1.0/tests/fixtures/smoke_ast_phase3.f90 +49 -0
  45. dimfort-0.1.0/tests/fixtures/smoke_basic.f90 +31 -0
  46. dimfort-0.1.0/tests/fixtures/smoke_check.f90 +15 -0
  47. dimfort-0.1.0/tests/fixtures/smoke_derived_types.f90 +24 -0
  48. dimfort-0.1.0/tests/fixtures/smoke_functions.f90 +45 -0
  49. dimfort-0.1.0/tests/fixtures/smoke_intrinsics.f90 +17 -0
  50. dimfort-0.1.0/tests/fixtures/smoke_rational_pow.f90 +14 -0
  51. dimfort-0.1.0/tests/integration/__init__.py +0 -0
  52. dimfort-0.1.0/tests/integration/test_cli_check.py +93 -0
  53. dimfort-0.1.0/tests/integration/test_overrides.py +55 -0
  54. dimfort-0.1.0/tests/integration/test_smoke_basic.py +52 -0
  55. dimfort-0.1.0/tests/unit/test_annotations.py +160 -0
  56. dimfort-0.1.0/tests/unit/test_attach.py +239 -0
  57. dimfort-0.1.0/tests/unit/test_cli.py +23 -0
  58. dimfort-0.1.0/tests/unit/test_config.py +199 -0
  59. dimfort-0.1.0/tests/unit/test_declarations.py +216 -0
  60. dimfort-0.1.0/tests/unit/test_format.py +36 -0
  61. dimfort-0.1.0/tests/unit/test_lsp_hover_scoping.py +66 -0
  62. dimfort-0.1.0/tests/unit/test_lsp_module_navigation.py +214 -0
  63. dimfort-0.1.0/tests/unit/test_lsp_server.py +87 -0
  64. dimfort-0.1.0/tests/unit/test_lsp_workset_cap.py +69 -0
  65. dimfort-0.1.0/tests/unit/test_source_io.py +51 -0
  66. dimfort-0.1.0/tests/unit/test_ts_checker.py +246 -0
  67. dimfort-0.1.0/tests/unit/test_ts_helpers.py +127 -0
  68. dimfort-0.1.0/tests/unit/test_ts_parser.py +304 -0
  69. dimfort-0.1.0/tests/unit/test_unit_config.py +49 -0
  70. dimfort-0.1.0/tests/unit/test_units.py +84 -0
  71. dimfort-0.1.0/tests/unit/test_var_units_scoping.py +170 -0
  72. dimfort-0.1.0/tests/unit/test_workspace_index.py +369 -0
@@ -0,0 +1,100 @@
1
+ name: release
2
+
3
+ # A single tag push (v*.*.*) gates the full release pipeline:
4
+ # 1. test: run pytest + ruff across Linux/macOS/Windows on
5
+ # Python 3.11 (our minimum supported version).
6
+ # 2. build: build sdist + wheel, check with twine.
7
+ # 3. publish-pypi: upload to PyPI via trusted publishing.
8
+ # 4. github-release: create a GitHub Release with the dist artefacts.
9
+ #
10
+ # Each downstream job `needs:` the prior, so a single failing test
11
+ # blocks publish — no broken release can land.
12
+ #
13
+ # `workflow_dispatch` lets you re-run the test matrix on demand
14
+ # (e.g. before tagging) without pushing a tag.
15
+
16
+ on:
17
+ push:
18
+ tags:
19
+ - "v*.*.*"
20
+ workflow_dispatch:
21
+
22
+ jobs:
23
+ test:
24
+ runs-on: ${{ matrix.os }}
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ os: [ubuntu-latest, macos-latest, windows-latest]
29
+ steps:
30
+ - uses: actions/checkout@v5
31
+ - uses: actions/setup-python@v6
32
+ with:
33
+ python-version: "3.11"
34
+ - name: Install
35
+ run: |
36
+ python -m pip install --upgrade pip
37
+ pip install -e ".[dev,lsp]"
38
+ - name: Lint
39
+ run: ruff check .
40
+ - name: Test
41
+ run: pytest
42
+
43
+ build:
44
+ needs: test
45
+ # Don't build/publish on manual dispatch — that's just a test re-run.
46
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v5
50
+ - uses: actions/setup-python@v6
51
+ with:
52
+ python-version: "3.12"
53
+ - name: Install build tooling
54
+ run: |
55
+ python -m pip install --upgrade pip
56
+ pip install build twine
57
+ - name: Build sdist and wheel
58
+ run: python -m build
59
+ - name: Check artefacts
60
+ run: twine check dist/*
61
+ - name: Upload artefacts
62
+ uses: actions/upload-artifact@v4
63
+ with:
64
+ name: dist
65
+ path: dist/
66
+
67
+ publish-pypi:
68
+ needs: build
69
+ runs-on: ubuntu-latest
70
+ # Trusted publishing — no API token in secrets. Configure the
71
+ # corresponding "pending publisher" on https://pypi.org/manage/account/publishing/
72
+ # before the first release.
73
+ environment:
74
+ name: pypi
75
+ url: https://pypi.org/project/dimfort/
76
+ permissions:
77
+ id-token: write
78
+ steps:
79
+ - uses: actions/download-artifact@v4
80
+ with:
81
+ name: dist
82
+ path: dist/
83
+ - uses: pypa/gh-action-pypi-publish@release/v1
84
+
85
+ github-release:
86
+ needs: build
87
+ runs-on: ubuntu-latest
88
+ permissions:
89
+ contents: write
90
+ steps:
91
+ - uses: actions/checkout@v5
92
+ - uses: actions/download-artifact@v4
93
+ with:
94
+ name: dist
95
+ path: dist/
96
+ - name: Create GitHub Release
97
+ uses: softprops/action-gh-release@v2
98
+ with:
99
+ files: dist/*
100
+ generate_release_notes: true
@@ -0,0 +1,243 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # --- DimFort-specific ---
221
+
222
+ # Per-project analysis cache (safe to delete; `dimfort cache clean` does the same)
223
+ .dimfort/
224
+
225
+ # OS files
226
+ .DS_Store
227
+ Thumbs.db
228
+ *~
229
+
230
+ # Editor / IDE
231
+ .idea/
232
+ *.swp
233
+ *.swo
234
+
235
+ # Local-only notes or scratch
236
+ NOTES.local.md
237
+ scratch/
238
+
239
+
240
+ # Emacs auto-save / backup
241
+ \#*\#
242
+ .\#*
243
+ *~
@@ -0,0 +1,92 @@
1
+ # Changelog
2
+
3
+ All notable changes to DimFort are documented here. Format inspired by [Keep a Changelog](https://keepachangelog.com/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] — 2026-05-19
8
+
9
+ First public release. Pre-alpha; expect breaking changes between
10
+ `0.1.x` versions as the tool matures against real-world Fortran
11
+ codebases.
12
+
13
+ ### Highlights
14
+
15
+ - **CLI**: `dimfort check FILE/DIR [...]` with per-file H-/U-summary;
16
+ `dimfort lsp` over stdio.
17
+ - **Annotation pipeline**: scoped per `(subroutine|function, name)` so
18
+ same-named parameters across two routines in a file don't alias.
19
+ - **Checker**: full H001-H004 (assignment, arithmetic, intrinsics,
20
+ user-defined calls, derived-type fields, rational `**` exponents)
21
+ across multi-file worksets.
22
+ - **Workspace orchestration**: `use`-chain resolution plus a
23
+ workspace-wide top-level-procedure index for F77-vintage external
24
+ procedures.
25
+ - **LSP server**: live diagnostics, hover (scope-aware bare
26
+ identifier, derived-type member chains, call signatures, module-
27
+ summary on `use foo`), inlay hints, go-to-definition for variables,
28
+ callables, and module names, code lens, completion inside
29
+ `@unit{...}`, "Add unit annotation" code action, the
30
+ `dimfort.checkWorkspace` command, didClose republish,
31
+ `workspace/inlayHint/refresh` push, tab-switch-safe republish, and
32
+ a `/tmp/dimfort-lsp.crash` excepthook for silent-crash diagnostics.
33
+ - **Editor companions** (separate repos): VSCode, Neovim ≥ 0.11,
34
+ Emacs (eglot + lsp-mode).
35
+ - **Project config**: `.dimfort.toml` with `[project] src_paths`,
36
+ `[workset] external_modules` / `max_size`, `[parser] cpp_defines`
37
+ / `include_paths`, `[units] file`.
38
+ - **Test coverage**: 228 unit + integration tests, ruff-clean.
39
+
40
+ ### 2026-05-19 — Scope-aware annotations, external-procedure index, tab-switch republish
41
+
42
+ - **Per-scope `@unit{}` annotations** (`attach.py`, `annotations.py`, `ts_checker.py`): annotations are now keyed by `(scope_lc, name)` where `scope_lc` is the lower-cased enclosing subroutine/function (or `None` at module/file level). Two routines in one file declaring same-named params with different units no longer alias. Flat `var_units` view retained as a back-compat first-seen surface for callers that don't carry scope info. `_make_scoped_lookup` no longer falls back to flat lookup when in scope-aware mode — this closed a real false-positive path where unannotated wrapper params (e.g. NetCDF `put_var(..., v)`) were absorbing the unit of unrelated same-named variables in the workset. Diagnostic count on LMDZ trial dropped from 20 to 12 H-findings, all real (8 spurious, retracted in `LMDZ_FINDINGS.md`).
43
+ - **Scope-aware hover** (`lsp/server.py`): bare-identifier hover consults the per-scope table and reports the *enclosing routine's* annotation, not the first-seen across the workset.
44
+ - **Module-name hover and goto-def** (`lsp/server.py`, `lsp/ts_helpers.py`): hover on the module-name token of a `use foo` statement renders a summary of the module's exports — variables with units, contained procedures with signatures, `(N/M annotated)` count when there's a gap. Goto-def on the same token jumps to the `module foo` header.
45
+ - **Workspace external-procedure index** (`core/workspace_index.py`, `core/multifile.py`, `lsp/server.py`): a workspace-wide name map from top-level `SUBROUTINE`/`FUNCTION` to defining file, populated at LSP startup (~4.5 s on full LMDZ). Resolves F77-vintage external procedures (called without a `USE` clause), so goto-def, hover signatures, and H004 all follow such calls. `resolve_workset`'s BFS now expands via `CALL` edges too; topo sort honours them; the per-file workset cap pins direct deps (modules used + procedures called) so shallow callees can't be sliced out.
46
+ - **Tab-switch-safe re-publish** (`lsp/server.py`): the single global `_last_result` was overwritten on every `didOpen`/`didSave`/`didChange`. Navigating caller↔callee opened the callee's tab, flipping the workset to its downward-only deps. Switching back was silent (no LSP event), so subsequent goto-def/hover/inlay on the caller failed with "not in trees". New `_ensure_uri_loaded` re-publishes synchronously when the requested URI isn't in the current workset.
47
+ - **H004 message includes argument name** (`ts_checker.py`): `"argument 5 (pbaru) unit mismatch: …"` instead of `"argument 5 unit mismatch: …"`. Index kept too — formal names can repeat across `INTENT(INOUT)` slots or in overloads, so position remains the unambiguous identifier and the name is the friendly hint.
48
+ - **Silent-crash trace hook** (`lsp/server.py`, opt-out via `DIMFORT_CRASH_LOG=""`): `sys.excepthook` + `threading.excepthook` + pygls/asyncio logger handlers mirror Python tracebacks into `/tmp/dimfort-lsp.crash`. Doesn't catch native segfaults / SIGKILLs, but makes future Python-level crashes immediately actionable.
49
+ - **Tree-handler serialisation lock** (`lsp/server.py`): defensive lock around `_hover`, `_definition`, and `_inlay_hint` so they can't traverse the same tree-sitter Tree from different threads. Today's bug turned out to be elsewhere, but the lock stays as cheap insurance against tree-sitter's C library not being thread-safe.
50
+
51
+ ### 2026-05-17 — CLI directory mode, LSP didClose persistence, U005 usage hint
52
+
53
+ - **CLI**: `dimfort check` accepts directory arguments and walks them
54
+ recursively for Fortran sources. New `--summary` flag prints a
55
+ per-file H-/U-diagnostic count breakdown after the diagnostic stream.
56
+ `FORTRAN_EXTS` and `discover_fortran_files` extracted to
57
+ `core/_source_io.py` so the LSP and CLI share one definition.
58
+ - **LSP**: `didClose` no longer publishes an empty diagnostic list for
59
+ the closed file — it now republishes the most recent workspace-check
60
+ diagnostics for that path, so the Problems panel keeps showing real
61
+ issues after the user closes a tab.
62
+ - **Checker**: U005 ("variable used in unit-checked expression but
63
+ has no `@unit{}` annotation") now appends `(e.g. used at line N)`
64
+ pointing at the earliest usage site, so the user can jump from the
65
+ unannotated declaration to a concrete consumer.
66
+ - **Branding**: `scripts/make_branding.py` renders a 1280×640
67
+ `social_preview.png` at the repo root. Design palette mirrors the
68
+ VSCompanion icon (translucent Clarendon F watermark, rounded
69
+ frame, `[m·s⁻²]` glyph).
70
+
71
+ ### Branch `ast-tree-sitter` (2026-05-16) — LFortran retired, tree-sitter takes over
72
+
73
+ Parser swap: LFortran subprocess → in-process tree-sitter Fortran grammar. The diagnostic pipeline, the LSP enrichments, and the on-disk caching are all re-implemented; CLI and config simplified accordingly.
74
+
75
+ - **Phase 0** (`df8a793`) — new `core.ts_parser`: parse_text / parse_file / walk, plus a CPP shim with line-map remap for `.F90` files. 18 unit tests pin the `&`-continuation drift case and the CPP shim's define/include/missing-include paths.
76
+ - **Phase 1** (`a823a73`) — declaration scanner ported. `core/annotations.py` walks tree-sitter `variable_declaration` and `derived_type_definition` nodes instead of the regex matcher; recovers names from `sized_declarator` / `init_declarator` wrappers. Net −155 / +174 lines; +1 test pinning the new "recover declarations after a syntax error" capability.
77
+ - **Phase 2** (`75459fd`) — full checker port. New `core/ts_checker.py` mirrors `core.ast_checker` 1:1 against tree-sitter nodes: `_resolve` for expressions, H001-H004 emitters, intrinsic dispatch, derived-type chain resolution, `**` exponent handling including negatives. `core/ast_multifile.py` switched to drive the new checker; 8 new unit tests at `tests/unit/test_ts_checker.py`.
78
+ - **Phase 3** (`d9d7c1c`) — LSP enrichments rewritten on tree-sitter. New `lsp/ts_helpers.py` (position containment, targeted walks, "is this the callee?" / "is this inside a declaration?" predicates). Hover, inlay hints, go-to-definition, and code-lens handlers all rewired; identifier-to-unit resolution shared with the diagnostic pipeline so there's a single source of truth. The most elaborate hover renderers (multi-variable expression / assignment hovers) intentionally skipped — they degrade to "no hover at that position" and can be reinstated later. Net +284 / −640.
79
+ - **Phase 4** (this commit) — LFortran path retired entirely. Deleted `core/lfortran.py`, `core/ast_checker.py`, `core/checker.py`, `core/ast_multifile.py`, `cache.py`, `core/parser.py`. New `core/symbols.py` holds the parser-agnostic data (FuncSig, intrinsic tables, ModuleExports, apply_use_clauses). `core/multifile.py` rewritten as a clean tree-sitter orchestrator (was the ASR orchestrator). CLI: `--backend`, `--lfortran`, `--no-cache`, `--cache-dir` flags removed; `cache` subcommand removed. Config: `[lfortran]` and `[checker]` sections silently ignored for backward compatibility but no longer exposed as fields. LSP: backend dispatch deleted, cache wiring deleted. Test count went from 287 → 183 — the deleted tests covered the deleted code.
80
+
81
+ ### Branch `ast-only` (previous, preserved on `ast_and_asr`)
82
+
83
+ - **Phase 0 (spike, 2026-05-15)** — minimal AST-only checker landing as `core.ast_checker.check`. Walks LFortran's AST (no ASR involvement, no `lfortran -c`) and emits H001 + H002 for `Name` / `Num` / `BinOp(+,-,*,/)` / `Assignment` node combinations. Demonstrated end-to-end on `tests/fixtures/smoke_check.f90`: H001 fires on the dimensionally-wrong assignment, not on the clean one. Design notes in `docs/ast-only-design.md`; rest of the H/U series, cross-file `use`-chain resolution, intrinsics, derived types, casts, and array sections are TBD across Phases 1–5.
84
+ - **Phase 1 (single-file H/U series, 2026-05-15)** — `core.ast_checker` extended to cover the full single-file H-series: H003 (dimensionless-intrinsic violation), H004 (call argument mismatch), plus `Pow` with constant exponent (integer or rational via `Fraction.limit_denominator`), `UnaryMinus`, `Real` literal, and the six intrinsic categories (`DIMENSIONLESS`, `TRANSFORMING`, `TRANSPARENT`, `SAME_UNIT_ARG`, `PRODUCT`, `REDUCTION`) re-used verbatim from `core.checker` — no duplication of intrinsic tables. `collect_function_signatures(ast, var_units)` walks the AST for `Function` / `Subroutine` definitions and builds the same `FuncSig` table the ASR-side checker produces; `check()` accepts a `signatures=` kwarg so Phase 2 can pass a workset-wide map. New fixture `tests/fixtures/smoke_ast_phase1.f90` and integration tests `test_ast_phase1.py` (5 tests). Added `test_ast_parity.py` (3 fixtures) asserting the AST checker's H-series multiset matches the ASR checker's on `smoke_check.f90` / `smoke_intrinsics.f90` / `smoke_functions.f90` — the parity guard that catches regression once Phase 2+ extends scope further.
85
+ - **Phase 2 (cross-file use-chains, 2026-05-15)** — `core.ast_multifile.check_files_ast` orchestrates a full workset using AST only (no `lfortran -c`, no ASR). `ast_checker.collect_module_exports(ast, var_units)` walks `Module` nodes and produces a `ModuleExports` record per module (vars + signatures); `ast_checker.apply_use_clauses(uses, exports, ...)` splices the imported symbols into a consumer file's scope, honouring `only:` lists and `local => remote` renames. Missing modules surface as U007. New integration tests `test_ast_phase2.py` (4 tests) cover the cross-file H001/H004 path, workset-wide H-series parity with the ASR pipeline, order-independence, and the U007 emission. All 231/231 tests still pass.
86
+ - **Phase 3 (derived types + arrays, 2026-05-15)** — `ast_checker` now resolves derived-type access chains (`a%b%c`), array elements (`a(i)`), array slices (`a(:)`, `a(1:n)`). Adds `collect_var_types(ast)` and `collect_type_field_types(ast)` to build the per-file type maps from `Declaration` and `DerivedType` nodes; the resolver walks `Name.member` chains against those maps to reach the `field_units` table. `FuncCallOrArray` whose name matches a known variable now returns that variable's unit — closing the "is `a(1)` a function call or array indexing?" ambiguity LFortran's AST inherits. Fix to `Pow` and the transforming-intrinsics codepath to use `Unit.pow(exp)` instead of `Unit ** exp` (the latter falls through to `Fraction.__rpow__` and crashes on `float`). Extended parity test set to 5 fixtures including `smoke_derived_types.f90` and `smoke_rational_pow.f90` — all pass. New fixture `smoke_ast_phase3.f90` + 3 Phase 3-specific tests. Full suite: 236/236.
87
+ - **Phase 3 hardening (2026-05-15)** — LMDZ trial on `dyn3d_common/` (117 files) surfaced two bugs in the Phase 2/3 multifile orchestrator: missing U-series emissions (U001 scan errors, U002 unit-parse failures, the U006/U-conflict/U010 set from `_attachment_diags`) and a cross-file bare-name leak through `merged_var_units`. Fixed by reusing `multifile._attachment_diags`, emitting U001/U002 in the per-file pass, and scoping each file's check from its own `attachment.var_units` (cross-file imports still arrive explicitly via `apply_use_clauses`). LMDZ impact on `dyn3d_common`: false-positive H001s dropped from 47 to 6; previously-suppressed H004s now surface (11). New regression fixture `tests/fixtures/multifile_scope/` + `test_ast_scope.py`.
88
+ - **Phase 4 (backend selection, 2026-05-15)** — `[checker] backend = "ast" \| "asr"` lands in `dimfort.config.DimfortConfig`. CLI gains `--backend ast\|asr` on the `check` subcommand. LSP server reads `backend` from `initializationOptions` (falling through to config, then default `"asr"`). VSCompanion repo's `ast-only` branch adds `dimfort.backend` (enum) to the settings schema and forwards it. Backend is logged in the init notification (`backend=…`). 5 new config tests + 3 new CLI integration tests. Default stays `"asr"`; Phase 5 will flip it once the AST path has soaked.
89
+ - **Phase 4.6 (`.intfb.h` stubs + cpp_defines, 2026-05-16)** — `[lfortran] include_paths` and `[lfortran] cpp_defines` in `DimfortConfig` thread `-I` and `-D` through to LFortran. Unblocks LMDZ's ecRad headers (after stubbing them empty) and the `#ifdef ISO`-wrapped isotope-branch modules. `lf.dump_tree` decodes stdout/stderr with UTF-8 → Latin-1 fallback so French-comment files don't crash the workspace check. Adds the "DimFort: Check Whole Workspace" LSP command with phase-tagged ($/progress) per-file reporting ("loading 412/2435", "indexing modules", "checking"). LMDZ trial: 2435 files → 16 unloadable + ~13 cascade U007s (all LFortran 0.63 bugs).
90
+ - **Phase 5 (default backend → AST, 2026-05-16)** — `cli.py`, `lsp/server.py`, and VSCompanion `package.json` all now default to `backend = "ast"`. ASR remains selectable via `--backend asr` (CLI), `[checker] backend = "asr"` (config), or the `dimfort.backend` VSCode setting. Fixes a long-standing round-trip bug in `ast_multifile`: it converted parsed `Unit` objects back to text via `format_unit()` before handing to `ast_checker.check`, which then re-parsed — but `format_unit` emits Unicode (`m/s²`, `kg×m/s²`) that the parser doesn't accept. `ast_checker.check` now accepts `Unit` objects directly for both `var_units` and `field_units`; the multifile path passes them through without round-tripping. Caught when the existing CLI integration tests (which previously ran via ASR by default) started failing — they exercise H001 on a single-file workset where this round-trip had been silently dropping the only annotation.
91
+ - **Phase 6a (parallel loading, 2026-05-16)** — `check_files_ast`'s Phase A now uses a `ThreadPoolExecutor` (default workers = `cpu_count() - 1`). Subprocess.run releases the GIL while LFortran is running, so threads parallelise without the pickling overhead a process pool would impose. Progress callback fires in completion order under a small lock. LMDZ benchmark (2435 files, 8 cores): 223s → 170s (1.3×). Modest gain — GIL contention during large-AST JSON parsing now dominates the residual.
92
+ - **Phase 6b (AST cache, 2026-05-16)** — New `cache.load_single_tree_cached(path, mode='ast', …)` mirrors `load_trees_cached` but caches one tree at a time. Stored under `<cache>/<sha1>.ast.json`, keyed on content sha256 mixed with `include_paths` + `cpp_defines` (so config changes invalidate cleanly). `ast_multifile.check_files_ast` now accepts a `cache_dir=` kwarg and threads it into `_load_one`; the LSP passes `_cache_dir` (already resolved at initialize). LSP buffer overrides bypass the cache for that file only — sibling files still benefit. 3 new unit tests covering round-trip, include-path invalidation, and cpp-define invalidation. Warm-run LMDZ workspace check now dominates JSON-load cost rather than LFortran, dropping wall time to a fraction of the cold run.
@@ -0,0 +1,89 @@
1
+ # Contributing to DimFort
2
+
3
+ Thanks for considering a contribution. DimFort is pre-alpha; the
4
+ contribution surface and expectations below will evolve as the
5
+ project stabilises.
6
+
7
+ ## Reporting issues
8
+
9
+ Open an issue on GitHub with:
10
+
11
+ - Minimum reproducible Fortran source (or a pointer to the
12
+ relevant file).
13
+ - The DimFort command you ran (or the editor + LSP context).
14
+ - Expected versus observed behaviour.
15
+ - Output of `dimfort --version` and your Python version.
16
+
17
+ If the failure is a false-positive diagnostic, include the
18
+ annotation in question and any cross-file imports involved — unit
19
+ consistency depends on the whole `use`-chain neighbourhood, not
20
+ just one file.
21
+
22
+ ## Development setup
23
+
24
+ ```bash
25
+ git clone https://github.com/ArrialVictor/DimFort.git
26
+ cd DimFort
27
+ python -m venv .venv
28
+ source .venv/bin/activate
29
+ pip install -e ".[dev,lsp]"
30
+ ```
31
+
32
+ Minimum Python version is 3.11. The Fortran parser
33
+ ([`tree-sitter-fortran`](https://pypi.org/project/tree-sitter-fortran/))
34
+ is a runtime dependency installed automatically.
35
+
36
+ ## Running tests
37
+
38
+ ```bash
39
+ pytest # 228 tests across unit + integration
40
+ pytest tests/unit -q # unit only
41
+ pytest -k <expression> # filter by name
42
+ ruff check . # lint
43
+ ```
44
+
45
+ A patch that breaks tests or trips ruff will not be merged.
46
+
47
+ ## Code style
48
+
49
+ - Follow the existing module organisation. See
50
+ [ARCHITECTURE.md](https://github.com/ArrialVictor/DimFort/blob/main/ARCHITECTURE.md)
51
+ (when present) and the docstrings at the top of each `core/`
52
+ module for the layout rationale.
53
+ - Comments should explain **why**, not what. Reading the code
54
+ tells you what; the comment is for the non-obvious constraint,
55
+ invariant, or trade-off.
56
+ - Public API additions need a docstring.
57
+ - New diagnostic codes are registered in `core/symbols.py`'s
58
+ `CODES` dict.
59
+ - Performance work is welcome; please include before/after numbers
60
+ on the LMDZ trial workset (see `project_workspace_perf.md` for
61
+ the benchmark recipe) so we know we're moving the needle.
62
+
63
+ ## Commits and pull requests
64
+
65
+ - Imperative subject under 70 characters
66
+ (`checker: do X`, not `did X`).
67
+ - Body explains motivation more than implementation. Mention
68
+ affected files, perf impact, test coverage, behavioural changes.
69
+ - One coherent change per commit. Use multiple commits in a PR if
70
+ the work has logically distinct phases.
71
+ - PRs should be rebased on the latest `main` before review.
72
+
73
+ ## Companions
74
+
75
+ The editor integrations live in their own repositories and have
76
+ their own contribution flows:
77
+
78
+ - [DimFort-VSCompanion](https://github.com/ArrialVictor/DimFort-VSCompanion)
79
+ - [DimFort-NvimCompanion](https://github.com/ArrialVictor/DimFort-NvimCompanion)
80
+ - [DimFort-EmacsCompanion](https://github.com/ArrialVictor/DimFort-EmacsCompanion)
81
+
82
+ A change that affects the LSP protocol surface needs a matching
83
+ companion change; mention it in the DimFort PR so reviewers can
84
+ coordinate.
85
+
86
+ ## License
87
+
88
+ By contributing, you agree your changes are licensed under the
89
+ project's MIT [LICENSE](LICENSE).
dimfort-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Victor Arrial
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.