Skip to main content

Dimensional homogeneity checker for Fortran projects, with LSP support.

Project description

DimFort

preview

CI License: MIT Python: 3.11+

Static unit-consistency checker for Fortran. You annotate declarations with the dimension they should carry, and DimFort verifies that assignments, arithmetic, intrinsics, and procedure calls all line up. Annotations are written as a custom Doxygen command, so a single source of truth feeds both the checker and your generated documentation.

real :: velocity  !< @unit{m/s}
real :: mass      !< @unit{kg}
real :: force     !< @unit{kg*m/s^2}

force = mass * velocity            ! diagnosed: force unit is kg, expected kg*m/s^2

Status: beta. Usable, tested, and proven against real-world Fortran — but the @unit{} format, diagnostic codes, and LSP protocol are not frozen yet and may still shift between 0.x releases. End-to-end, these work today: the annotation scanner, attachment pass, the full H-series checker (H001–H004 + H010 + H020–H023 polymorphism family), the unit-algebra wrapper rules for LOG / EXP-tagged quantities (D1.2 – D1.6), parametric polymorphism over function signatures ('a, 'b, … with Hindley-Milner unification per call site), per-rule provenance traces, intrinsics, user-defined function and subroutine calls, derived-type field access, rational ** exponents, multi-file worksets, a workspace-aware LSP server with live-edit diagnostics, per-surface hover (call / subroutine / expression, each Short or Detailed with a formal-vs-actual pairing or full unit-algebra trace), inlay hints, go-to-definition, code actions, completion, and a CLI that accepts files or directories.

Adopting on an existing codebase

Many real-world Fortran projects already document units in inline comments — ! [m/s], ! horizontal wind speed [m/s], ! tracer ratio [m^2: Andreas 1989]. DimFort can read your project's own convention, so you don't rewrite every declaration just to opt in.

Add a few lines to dimfort.toml:

[parser.unit_comments]
unit = [
  { open = "@unit{", close = "}" },
  { open = "[",      close = "]" },
]

Now ! [m/s] is a first-class unit annotation, checked exactly like @unit{m/s}. The same table handles unit_assume, unit_affine, and their nonunit / nonunit_assume / nonunit_affine filter lists — each on its own key with its own opt-in. Full recipe in Bringing DimFort to an existing codebase.

Quick tour

Want a hands-on look first? demos/tour.f90 is a short, self-contained file that exercises the most common DimFort diagnostics on a textbook moist-thermodynamics routine.

dimfort check --scale demos/tour.f90

demos/README.md walks through the output line by line.

In an editor, the same analysis renders in an always-visible side panel that follows the cursor — every position shows the unit-algebra tree for the current expression, alongside the diagnostics on that line, the variables in scope, and the modules in scope:

DimFort side panel in VSCode — unit-algebra tree for the cursor's expression with stacked scope and imports below, plus per-file and project-wide coverage in the footer

The footer bar carries per-file and project-wide coverage tiers (verified / unverified / violation / unparsed), updated live as you edit. See Editor integrations below.

Install

DimFort is published on PyPI as dimfort. It's a CLI tool with an entry point — install it isolated with pipx:

pipx install 'dimfort[lsp]'      # includes the LSP server extra
dimfort --version

The [lsp] extra pulls in pygls; omit it if you only want the CLI and never the language server.

On macOS with Homebrew Python: PEP 668 makes plain pip install refuse to touch Homebrew's site-packages. Use pipx as shown above, or install into a virtual environment. If pipx install says "command not found", run brew install pipx && pipx ensurepath first.

From source (contributors)

The canonical workflow uses uv and the committed uv.lock:

git clone https://github.com/ArrialVictor/DimFort.git
cd DimFort
uv sync --extra dev --extra lsp

The plain-pip equivalent also works — see CONTRIBUTING.md for both paths.

Requirements

Python ≥ 3.11. The Fortran parser (tree-sitter-fortran) is a runtime dependency installed automatically — no external compiler or subprocess needed for parsing. For .F90 files using CPP #-directives DimFort shells out to the system cpp if [parser] cpp_defines or [parser] include_paths are set in dimfort.toml.

Usage

dimfort init -t climate              # generate dimfort.toml from a discipline template
dimfort check path/to/file.f90       # check a single file
dimfort check path/to/project/       # walk a directory recursively
dimfort check path/...  --summary    # also print a per-file H/U count table
dimfort interactions <var> path/...  # cross-site unit report for one variable
dimfort lsp                          # start the language server (stdio)

dimfort init writes a project dimfort.toml that opens with the SI core (inherited from the shipped defaults) and includes all five discipline templates — selected ones uncommented and ready to use, the rest commented out in the same file for in-file discovery. Pass -t climate,astronomy to activate multiple, --bare to skip templates entirely, or --dry-run to print the result without writing.

interactions is an on-demand query: for a single variable it lists every site that reads or writes it across the workset — grouped into Declaration / Write / Read / Undetermined, each with the unit that site implies — and emits X001 when two sites make conflicting unit claims (which the per-statement check can't see, since it fires even on unannotated variables).

Exit code is 0 if no errors, 1 if any error-severity diagnostic was produced, 2 for usage / file / config errors. Warnings alone do not fail the run.

Diagnostics are grouped by code prefix: H for homogeneity, U for annotation-pipeline problems, S for the opt-in scale family, X for cross-site findings from dimfort interactions, and P for parser-skipped regions. Full reference (every code, severity, and trigger) lives at docs/reference/diagnostic-codes.md. The unit-algebra rule taxonomy (D1.1D1.7) that classifies why a homogeneity diagnostic fires is at docs/reference/unit-algebra.md.

Doxygen integration

Annotations are read from Doxygen comments (!> / !! preceding a declaration, or !< trailing it) and apply to every variable in a declaration list. To make Doxygen render them natively, add one line to your Doxyfile:

ALIASES += "unit{1}=\par Unit:^^\1"

Module-level constants follow the same notation:

!> @brief Gravitational acceleration.
!> @unit{m/s^2}
real, parameter :: g = 9.81

For quantities that live in log or exp space, wrap the inner unit with LOG(...) or EXP(...):

real :: lp     !< @unit{LOG(Pa)}
real :: tau    !< @unit{EXP(K)}

DimFort tracks the wrapper through arithmetic: LOG(psol) + LOG(pref) types as LOG(Pa²), LOG(p1) − LOG(p2) collapses to dimensionless via the pressure-ratio rule, and EXP(LOG(psol) − ...) cancels back to Pa. The full rule set is in docs/reference/unit-algebra.md.

Trace mode

Pass --trace to see the rule chain behind each diagnostic:

dimfort check --trace src/my_module.f90

Each error / warning prints the firing rule IDs (R3.1, R5.6, …) under the message. The VSCode extension surfaces the same trace in hover, and lets you pick the depth per surface — call, subroutine, expression — via the DimFort: Hover settings (Short for a one-line summary, Detailed for the full unit-algebra tree with per-row 🟢/🟡/🔴 markers).

Detailed expression hover on tour.f90 line 42 — H001 assignment mismatch m·s⁻¹ ≠ m²·s⁻² with the rule trace surfaced under each row

See docs/editor-integration/hover-ui.md for the layout spec.

See docs/reference/annotations.md for the full reference: unit-expression grammar, continuation-line forms, declaration lists, and the diagnostic codes the scanner can emit.

Documentation

Editor integrations

Thin LSP clients that wire dimfort lsp into common editors. Each lives in its own repository, releases on its own cadence, and shares the same feature surface — diagnostics, hover, inlay hints, go-to-definition, code actions, completion, plus the side panel (cursor-following unit-algebra tree + scope / imports tables) and the coverage layer introduced in 0.2.4–0.2.5: per-line gutter / tint showing what DimFort knows about each line, and a footer bar with file + workspace coverage statistics.

  • VSCode — on the Visual Studio Marketplace (ext install arrialvictor.dimfort-vscode) and on Open VSX for VSCodium / Cursor / Theia / code-server.
  • Neovim (≥ 0.11) — install via any plugin manager pointing at the repo.
  • Emacs — install via straight.el / use-package or manual require. Works with both eglot (Emacs 29+) and lsp-mode.

License

See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dimfort-0.2.7.tar.gz (19.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dimfort-0.2.7-py3-none-any.whl (389.6 kB view details)

Uploaded Python 3

File details

Details for the file dimfort-0.2.7.tar.gz.

File metadata

  • Download URL: dimfort-0.2.7.tar.gz
  • Upload date:
  • Size: 19.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dimfort-0.2.7.tar.gz
Algorithm Hash digest
SHA256 085cbad4af850e74698f30b41417d4f83bae52f1eaecee174ac38b083f5d0ce8
MD5 08930854bbe0f0d650b13767a95545bf
BLAKE2b-256 d1d16a64fd002a963e0861aaad480c8a4af0ffb4dc4f3191c71847c4f0d93fba

See more details on using hashes here.

Provenance

The following attestation bundles were made for dimfort-0.2.7.tar.gz:

Publisher: release.yml on ArrialVictor/DimFort

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dimfort-0.2.7-py3-none-any.whl.

File metadata

  • Download URL: dimfort-0.2.7-py3-none-any.whl
  • Upload date:
  • Size: 389.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dimfort-0.2.7-py3-none-any.whl
Algorithm Hash digest
SHA256 cafb808c3ad341ab51d4ea0a17417d623ad3558971cc3d6964f64dc0f46788cb
MD5 99fd60c8d220f6e1eddc9db2f631923a
BLAKE2b-256 8decaaa4787d80fb436fed3f60aeadec1ab73cef89648faaa0e0c4ba28de29d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for dimfort-0.2.7-py3-none-any.whl:

Publisher: release.yml on ArrialVictor/DimFort

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page