|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Fable is an F# to multi-language compiler that transpiles F# code to JavaScript, TypeScript, Python, Rust, Dart, PHP, and Erlang/BEAM. It uses a fork of F# Compiler Services (FCS) with custom tweaks, located in `lib/fcs`. |
| 8 | + |
| 9 | +## Build Commands |
| 10 | + |
| 11 | +The build system is implemented in F# at `src/Fable.Build/`. All commands go through `build.sh` (or `build.bat` on Windows): |
| 12 | + |
| 13 | +```bash |
| 14 | +./build.sh # Show all available commands |
| 15 | + |
| 16 | +# Build runtime libraries |
| 17 | +./build.sh fable-library --javascript # Also: --typescript, --python, --dart, --rust, --beam |
| 18 | + |
| 19 | +# Run tests (targets: javascript, typescript, python, dart, rust, beam, integration) |
| 20 | +./build.sh test javascript |
| 21 | +./build.sh test javascript --watch # Watch mode (recompile on changes) |
| 22 | +./build.sh test javascript --skip-fable-library # Skip fable-library rebuild (only if fable-library source is unchanged) |
| 23 | + |
| 24 | +# Python-specific options |
| 25 | +./build.sh test python --skip-fable-library-core # Skip slow Rust extension + .pyi rebuild (Python only) |
| 26 | +./build.sh test python --type-check # Run Pyright type checking on Python output |
| 27 | + |
| 28 | +# Quick iteration (watch mode on a minimal project) |
| 29 | +./build.sh quicktest javascript # Also: typescript, python, dart, rust, beam |
| 30 | +``` |
| 31 | + |
| 32 | +`--skip-fable-library` is safe when changes are only in `src/Fable.Transforms/` or other compiler code. If you modified runtime library source (e.g., `src/fable-library-py/`, `src/fable-library-ts/`), do not skip. If you already ran `./build.sh fable-library` separately, use `--skip-fable-library` when running tests right afterwards to avoid rebuilding. |
| 33 | + |
| 34 | +Build output goes to `temp/`: transpiled runtime libraries in `temp/fable-library-<target>/` and test output in `temp/tests/<target>/` (e.g., `temp/fable-library-beam/` and `temp/tests/beam/`). |
| 35 | + |
| 36 | +Test runners by target: JavaScript/TypeScript use Mocha, Python uses pytest (with `uv`, not pip), Rust uses cargo test, Dart uses `dart test`. |
| 37 | + |
| 38 | +## Architecture |
| 39 | + |
| 40 | +### Compiler Pipeline |
| 41 | + |
| 42 | +```text |
| 43 | +F# Source → FCS Parser → F# AST → FSharp2Fable → Fable AST → FableTransforms → Fable2Target → Target AST → Printer → Output |
| 44 | +``` |
| 45 | + |
| 46 | +1. **FSharp2Fable** (`src/Fable.Transforms/FSharp2Fable.fs`): F# AST → Fable intermediate AST |
| 47 | +2. **FableTransforms** (`src/Fable.Transforms/FableTransforms.fs`): Optimizations on Fable AST (tail-call detection, monadic trampolines, etc.) |
| 48 | +3. **Fable2Target**: Language-specific code generators, each in their own subdirectory: |
| 49 | + - `Fable2Babel.fs` → JavaScript/TypeScript (outputs Babel AST, printed by `BabelPrinter.fs`) |
| 50 | + - `Python/Fable2Python.*.fs` → Python (AST in `Python.AST.fs`, printed by `PythonPrinter.fs`) |
| 51 | + - `Rust/Fable2Rust.fs` → Rust |
| 52 | + - `Dart/Fable2Dart.fs` → Dart |
| 53 | + - `Beam/Fable2Beam.fs` → Erlang/BEAM |
| 54 | + |
| 55 | +### Replacements System |
| 56 | + |
| 57 | +`Replacements.fs` files map .NET BCL method calls to target language implementations. This is how `System.String.Contains()` becomes the appropriate call in each target. Each target has its own replacements file: |
| 58 | + |
| 59 | +- `src/Fable.Transforms/Replacements.fs` — JavaScript/TypeScript (default) |
| 60 | +- `src/Fable.Transforms/Python/Replacements.fs` |
| 61 | +- `src/Fable.Transforms/Rust/Replacements.fs` |
| 62 | +- `src/Fable.Transforms/Dart/Replacements.fs` |
| 63 | +- `src/Fable.Transforms/Beam/Replacements.fs` |
| 64 | + |
| 65 | +Shared replacement utilities are in `Replacements.Util.fs` and `Replacements.Api.fs`. |
| 66 | + |
| 67 | +### Key Source Directories |
| 68 | + |
| 69 | +- `src/Fable.AST/` — Core AST definitions (`Fable.fs` defines the Fable intermediate representation with types like `Expr`, `Type`, `MemberDecl`) |
| 70 | +- `src/Fable.Core/` — Attributes and interop types used in F# source code targeting Fable (e.g., `[<Emit>]`, `[<Import>]`) |
| 71 | +- `src/Fable.Transforms/` — All compilation stages, organized by target language in subdirectories |
| 72 | +- `src/Fable.Compiler/` — Compiler library (project cracking, file watching) |
| 73 | +- `src/Fable.Cli/` — CLI entry point; `Pipeline.fs` orchestrates file compilation and output |
| 74 | +- `src/fable-library-{ts,py,dart,rust,beam}/` — Runtime libraries for each target. Parts are written in F# (e.g., `src/fable-library-ts/Seq.fs`) and transpiled to the target language during build. Other targets may share F# sources from `fable-library-ts/` via linked files in their `.fsproj` (e.g., `src/fable-library-beam/Fable.Library.Beam.fsproj` links `Seq.fs` from `fable-library-ts/`). The transpiled output goes to `temp/fable-library-<target>/` |
| 75 | +- `src/quicktest*/` — Minimal projects for rapid development iteration |
| 76 | + |
| 77 | +### Per-Target Structure |
| 78 | + |
| 79 | +Each target language follows a consistent pattern within `src/Fable.Transforms/`: |
| 80 | + |
| 81 | +- `Fable2<Target>.fs` — Main compilation from Fable AST to target AST |
| 82 | +- `<Target>.AST.fs` — Target language AST definition |
| 83 | +- `<Target>Printer.fs` — Target AST → source code string |
| 84 | +- `Replacements.fs` — .NET BCL → target language mappings |
| 85 | + |
| 86 | +## Development Guidelines |
| 87 | + |
| 88 | +When investigating an issue or implementing a feature for a specific target, always look at how other targets handle the same problem. Targets should stay aligned in how they solve similar features — check the equivalent `Fable2<Target>.fs` and `Replacements.fs` files for other languages before implementing. |
| 89 | + |
| 90 | +The main transform files (`Fable2Babel.fs`, `Fable2Beam.fs`, `Fable2Python.Transforms.fs`, etc.) should contain only Fable AST to target AST transformations. Helper functions and utilities belong in the corresponding `.Util.fs`, `.Reflection.fs`, or similar files. |
| 91 | + |
| 92 | +Be careful when modifying shared files like `FableTransforms.fs`, `Transforms.Util.fs`, `FSharp2Fable.fs`, `Replacements.Api.fs`, or `Replacements.Util.fs` — these are used by all targets, and changes can introduce regressions for targets other than the one being fixed. |
| 93 | + |
| 94 | +- **`src/Fable.AST/`** — Do not modify. Changes are breaking and require a new major version of Fable. |
| 95 | +- **`src/Fable.Core/`** — Distributed on NuGet. Must not have breaking changes within a major version. |
| 96 | + |
| 97 | +`src/fable-library-py/` is distributed on PyPI and must be backward-compatible within minor versions. Do not change the semantics of existing functions — instead, create a new function with a new name and update `Replacements.fs` to call it. |
| 98 | + |
| 99 | +All code in `fable-library-py` must be fully type-annotated using Python 3.12+ syntax (PEP 695). Avoid `Any`, `cast()`, and `# type: ignore` — these require justification. Keep functions small and simple: extract helpers and use early returns. The Rust extension modules (`fable-library-core`) exist to provide correct .NET semantics for numerics, arrays, etc. — not for performance (the FFI crossing is expensive). |
| 100 | + |
| 101 | +All transpiled Python code is type-checked with Pyright at standard settings (`./build.sh test python --type-check`). Do not introduce type-checking regressions without justification. |
| 102 | + |
| 103 | +## Development Workflow |
| 104 | + |
| 105 | +**Quicktest** is the fastest way to iterate (seconds vs minutes for full tests). Use it to investigate complex problems: |
| 106 | + |
| 107 | +1. Edit `src/quicktest/QuickTest.fs` (or `src/quicktest-py/QuickTest.fs`, etc.) |
| 108 | +2. Ask the user to run `./build.sh quicktest <target>` — this starts a watcher that never exits, so do not run it yourself |
| 109 | +3. Output goes to `temp/tests/` |
| 110 | + |
| 111 | +Quicktest is also preferred when adding debug output (e.g., `printfn` in compiler code) since running full tests with debug prints produces too much output. |
| 112 | + |
| 113 | +**Test suites** are in `tests/<target>/` (e.g., `tests/Python/`, `tests/Js/Main/`). Tests are first run on .NET, then transpiled to `temp/tests/<target>/` and executed with the target's test runner. Full test runs take several minutes. |
| 114 | + |
| 115 | +When adding a test, check if other targets already have a test for the same case (e.g., look in `tests/Js/Main/` before adding to `tests/Python/`) — reuse or adapt existing test patterns where possible. |
| 116 | + |
| 117 | +**Never modify a test to make it pass on a target if it already passes on .NET.** If a test passes on .NET but fails on a target, the compiler or runtime library needs to be fixed — not the test. |
| 118 | + |
| 119 | +## Changelogs |
| 120 | + |
| 121 | +PRs must update changelogs. Always update both `src/Fable.Cli/CHANGELOG.md` and `src/Fable.Compiler/CHANGELOG.md` for changes to Fable.Transforms or fable-library. If the change touches `Fable.Core` (attributes, interop types), also update `src/Fable.Core/CHANGELOG.md`. Add entries under the `## Unreleased` section following the Keep a Changelog format with target prefix (e.g., `* [Python] Fix ...`, `* [Beam] Add ...`, `* [All] Fix ...`). |
| 122 | + |
| 123 | +## Requirements |
| 124 | + |
| 125 | +- .NET SDK 10+ |
| 126 | +- Node.js with npm |
| 127 | +- Python 3.12+ with [uv](https://docs.astral.sh/uv/) (3.12+ required for PEP 695 type parameter syntax) |
| 128 | +- Rust toolchain (for Rust target) |
| 129 | +- Dart SDK (for Dart target) |
| 130 | +- Erlang/OTP with rebar3 (for BEAM target) |
0 commit comments