{"owner":"rescript-lang","repo":"rescript","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI coding assistants when working with code in this repository.\n\n## Quick Start: Essential Commands\n\n```bash\n# Build the platform toolchain (default target)\nmake\n\n# Build the platform toolchain + stdlib\nmake lib\n\n# Build the platform toolchain + stdlib and run tests\nmake test\n\n# Format code\nmake format\n\n# Check formatting\nmake checkformat\n```\n\nThe Makefile’s targets build on each other in this order:\n\n1. `yarn-install` runs automatically for targets that need JavaScript tooling (lib, playground, tests, formatting, etc.).\n2. `build` (default target) builds the toolchain binaries (all copied into `packages/@rescript/<platform>/bin`):\n   - `compiler` builds the dune executables (`bsc`, `rescript-*`, `ounit_tests`, etc.).\n   - `rewatch` builds the Rust-based ReScript build system and CLI.\n3. `lib` uses those toolchain outputs to build the runtime sources.\n4. Test targets (`make test`, `make test-syntax`, etc.) reuse everything above.\n\n## ⚠️ Critical Guidelines & Common Pitfalls\n\n- **We are NOT bound by OCaml compatibility** - The ReScript compiler originated as a fork of the OCaml compiler, but we maintain our own AST and can make breaking changes. Focus on what's best for ReScript's JavaScript compilation target.\n\n- **Never modify `parsetree0.ml`** - Existing PPX (parser extensions) rely on this frozen v0 version. When changing `parsetree.ml`, always update the mapping modules `ast_mapper_from0.ml` and `ast_mapper_to0.ml` to maintain PPX compatibility while allowing the main parsetree to evolve\n\n- **Missing test coverage** - Always add tests for syntax, lambda, and end-to-end behavior\n\n- **Test early and often** - Add tests immediately after modifying each compiler layer to catch problems early, rather than waiting until all changes are complete\n\n- **Use underscore patterns carefully** - Don't use `_` patterns as lazy placeholders for new language features that then get forgotten. Only use them when you're certain the value should be ignored for that specific case. Ensure all new language features are handled correctly and completely across all compiler layers\n- **Avoid `let _ = …` for side effects** - If you need to call a function only for its side effects, use `ignore expr` (or bind the result and thread state explicitly). Do not write `let _ = expr in ()`, and do not discard stateful results—plumb them through instead.\n\n- **Don't use unit `()` with mandatory labeled arguments** - When a function has a mandatory labeled argument (like `~config`), don't add a trailing `()` parameter. The labeled argument already prevents accidental partial application. Only use `()` when all parameters are optional and you need to force evaluation. Example: `let forceDelayedItems ~config = ...` not `let forceDelayedItems ~config () = ...`\n\n- **Be careful with similar constructor names across different IRs** - Note that `Lam` (Lambda IR) and `Lambda` (typed lambda) have variants with similar constructor names like `Ltrywith`, but they represent different things in different compilation phases.\n\n- **Avoid warning suppressions** - Never use `[@@warning \"...\"]` to silence warnings. Instead, fix the underlying issue properly\n- **Skip trailing `; _` in record patterns** - The warning it targets is disabled in this codebase, so prefer `{field = x}` over `{field = x; _}`.\n\n- **Do not introduce new keywords unless absolutely necessary** - Try to find ways to implement features without reserving keywords, as seen with the \"catch\" implementation that avoids making it a keyword.\n\n## Compiler Architecture\n\n### Compilation Pipeline\n\n```\nReScript Source (.res)\n  ↓ (ReScript Parser - compiler/syntax/)\nSurface Syntax Tree\n  ↓ (Frontend transformations - compiler/frontend/)\nSurface Syntax Tree\n  ↓ (OCaml Type Checker - compiler/ml/)\nTypedtree\n  ↓ (Lambda compilation - compiler/core/lam_*)\nLambda IR\n  ↓ (JS compilation - compiler/core/js_*)\nJS IR\n  ↓ (JS output - compiler/core/js_dump*)\nJavaScript Code\n```\n\n### Platform-specific compiler modules\n\nThe Dune `browser` profile builds the playground compiler. Platform-dependent\nmodules are stored below `platform/native/` and `platform/playground/` in their\nowning compiler directory. Rules in that directory's `dune` file copy the\nselected implementation into the build directory as an ordinary `.ml` module;\nall other profiles select the native source. Generated module paths in errors\nor stack traces therefore map back to one of those two source directories.\n\n### Key Directory Structure\n\n```\ncompiler/\n├── syntax/          # ReScript syntax parser (MIT licensed)\n├── frontend/        # AST transformations, FFI processing\n├── ml/              # OCaml compiler infrastructure\n├── core/            # Core compilation (lam_*, js_* files)\n├── ext/             # Extended utilities and data structures\n└── gentype/         # TypeScript generation\n\nanalysis/            # Language server and tooling\npackages/@rescript/\n├── runtime/         # Runtime and standard library\n└── <platform>/      # Platform-specific binaries\n\ntests/\n├── syntax_tests/    # Parser/syntax layer tests\n├── tests/           # Runtime library tests\n├── build_tests/     # Integration tests\n└── ounit_tests/     # Compiler unit tests\n```\n\n## Working on the Compiler\n\n### Development Workflow\n\n1. **Understand which layer you're working on:**\n   - **Syntax layer** (`compiler/syntax/`): Parsing and surface syntax\n   - **ML layer** (`compiler/ml/`): Type checking and AST transformations\n   - **Lambda layer** (`compiler/core/lam_*`): Intermediate representation and optimizations\n   - **JS layer** (`compiler/core/js_*`): JavaScript generation\n\n2. **Always run appropriate tests:**\n\n   ```bash\n   # For compiler or stdlib changes\n   make test\n\n   # For syntax changes\n   make test-syntax\n\n   # For specific test types\n   make test-syntax-roundtrip\n   make test-gentype\n   make test-analysis\n   ```\n\n3. **Test your changes thoroughly:**\n   - Syntax tests for new language features\n   - Integration tests for behavior changes\n   - Unit tests for utility functions\n   - Always check JavaScript output quality\n\n4. **Add a `CHANGELOG.md` entry** for any user-facing change (bug fix, feature, or breaking change). Put it under the matching section of the current `(Unreleased)` version and end the line with the PR link. See [CONTRIBUTING.md](CONTRIBUTING.md). PRs are expected to include one.\n\n### Debugging Techniques\n\n#### View Intermediate Representations\n\n```bash\n# Source code (for debugging preprocessing)\n./cli/bsc.js -dsource myfile.res\n\n# Parse tree (surface syntax after parsing)\n./cli/bsc.js -dparsetree myfile.res\n\n# Typed tree (after type checking)\n./cli/bsc.js -dtypedtree myfile.res\n\n# Raw lambda (unoptimized intermediate representation)\n./cli/bsc.js -drawlambda myfile.res\n\n# Use lambda printing for debugging (add in compiler/core/lam_print.ml)\n```\n\n#### Common Debug Scenarios\n\n- **JavaScript formatting issues**: Check `compiler/ml/pprintast.ml`\n- **Type checking issues**: Look in `compiler/ml/` type checker modules\n- **Optimization bugs**: Check `compiler/core/lam_*.ml` analysis passes\n- **Code generation bugs**: Look in `compiler/core/js_*.ml` modules\n\n### Testing Requirements\n\n#### When to Add Tests\n\n- **Always** for new language features\n- **Always** for bug fixes\n- **When modifying** analysis passes\n- **When changing** JavaScript generation\n\n#### Test Types to Include\n\n1. **Syntax tests** (`tests/syntax_tests/`) - Parser validation\n2. **Integration tests** (`tests/tests/`) - End-to-end behavior\n3. **Unit tests** (`tests/ounit_tests/`) - Compiler functions\n4. **Build tests** (`tests/build_tests/`) - Error cases and edge cases\n5. **Type tests** (`tests/build_tests/super_errors/`) - Single-file type checking errors\n6. **Multi-file error tests** (`tests/build_tests/super_errors_multi/`) - Cross-module errors that need separate `.res` / `.resi` files\n\n#### Error variant catalog\n\n[`tests/ERROR_VARIANTS.md`](tests/ERROR_VARIANTS.md) is a per-module\ncatalog of every error and warning variant the compiler can emit, with\neach entry mapped to a fixture (or a documented reason it's unreachable).\n\n**When adding or removing an error variant**, also update the catalog:\n\n1. Add (or remove) the row in the relevant module section.\n2. Set the status (`✓` covered / `⚠` unreachable / `☐` TODO).\n3. If covered, link the fixture path; if unreachable, note the reason.\n\n**When adding or removing a fixture**, update the corresponding row's\n`Fixture` and status columns so the catalog stays in sync with the test\nsuite. The catalog is the primary tool for finding coverage gaps and\ndead-code removal candidates; stale entries make both jobs harder.\n\n## Build Commands & Development\n\n### Essential Commands\n\n```bash\n# Build compiler\nmake\n\n# Build compiler in watch mode\nmake watch\n\n# Build compiler and standard library\nmake lib\n\n# Build compiler and standard library and run all tests\nmake test\n\n# Build artifacts and update artifact list\nmake artifacts\n\n# Clean build\nmake clean\n```\n\n### Testing Commands\n\n```bash\n# Specific test types\nmake test-syntax           # Syntax parser tests\nmake test-syntax-roundtrip # Roundtrip syntax tests\nmake test-gentype         # GenType tests\nmake test-analysis        # Analysis tests\nmake test-tools           # Tools tests\nmake test-rewatch         # Rewatch tests\n\n# Single file debugging\n./cli/bsc.js myfile.res\n```\n\n### Code Quality\n\n```bash\n# Format code\nmake format\n\n# Check formatting\nmake checkformat\n\n# Lint with Biome\nnpm run check\nnpm run check:all\n\n# TypeScript type checking\nnpm run typecheck\n```\n\n## Performance Considerations\n\nThe compiler is designed for fast feedback loops and scales to large codebases:\n\n- **Avoid meaningless symbols** in generated JavaScript\n- **Maintain readable JavaScript output**\n- **Consider compilation speed impact** of changes\n- **Use appropriate optimization passes** in Lambda and JS IRs\n- **Profile** before and after performance-related changes\n\n## Coding Conventions\n\n### Naming\n\n- **OCaml code**: snake_case (e.g., `to_string`)\n- **ReScript code**: camelCase (e.g., `toString`)\n\n### Commit Standards\n\n- Use DCO sign-off: `Signed-Off-By: Your Name <email>`\n- Include appropriate tests with all changes\n- Build must pass before committing\n\n### Code Quality\n\n- Follow existing patterns in the codebase\n- Prefer existing utility functions over reinventing\n- Comment complex algorithms and non-obvious logic\n- Maintain backward compatibility where possible\n\n## Development Environment\n\n- **OCaml**: 5.3.0+ with opam\n- **Build System**: dune with profiles (dev, release, browser)\n- **JavaScript**: Node.js 20+ for tooling\n- **Rust**: Toolchain needed for rewatch\n\n## Common Tasks\n\n### Adding New Language Features\n\n1. Update parser in `compiler/syntax/`\n2. Update AST definitions in `compiler/ml/`\n3. Implement type checking in `compiler/ml/`\n4. Add Lambda IR handling in `compiler/core/lam_*`\n5. Implement JS generation in `compiler/core/js_*`\n6. Add comprehensive tests\n\n### Debugging Compilation Issues\n\n1. Identify which compilation phase has the issue\n2. Use appropriate debugging flags (`-dparsetree`, `-dtypedtree`)\n3. Check intermediate representations\n4. Add debug output in relevant compiler modules\n5. Verify with minimal test cases\n\n### Working with Lambda IR\n\n- Remember Lambda IR is the core optimization layer\n- All `lam_*.ml` files process this representation\n- Use `lam_print.ml` for debugging lambda expressions\n- Test both with and without optimization passes\n\n## Working on the Build System\n\n### Rewatch Architecture\n\nRewatch is ReScript's build system written in Rust. It provides fast incremental builds, better error messages, and improved developer experience.\n\n#### Key Components\n\n```\nrewatch/src/\n├── build/              # Core build system logic\n│   ├── build_types.rs  # Core data structures (BuildState, Module, etc.)\n│   ├── compile.rs      # Compilation logic and bsc argument generation\n│   ├── parse.rs        # AST generation and parser argument handling\n│   ├── packages.rs     # Package discovery and dependency resolution\n│   ├── deps.rs         # Dependency analysis and module graph\n│   ├── clean.rs        # Build artifact cleanup\n│   └── logs.rs         # Build logging and error reporting\n├── cli.rs              # Command-line interface definitions\n├── config.rs           # rescript.json configuration parsing\n├── watcher.rs          # File watching and incremental builds\n└── main.rs             # Application entry point\n```\n\n#### Build System Flow\n\n1. **Initialization** (`build::initialize_build`)\n   - Parse `rescript.json` configuration\n   - Discover packages and dependencies\n   - Set up compiler information\n   - Create initial `BuildState`\n\n2. **AST Generation** (`build::parse`)\n   - Generate AST files using `bsc -bs-ast`\n   - Handle PPX transformations\n   - Process JSX\n\n3. **Dependency Analysis** (`build::deps`)\n   - Analyze module dependencies from AST files\n   - Build dependency graph\n   - Detect circular dependencies\n\n4. **Compilation** (`build::compile`)\n   - Generate `bsc` compiler arguments\n   - Compile modules in dependency order\n   - Handle warnings and errors\n   - Generate JavaScript output\n\n5. **Incremental Updates** (`watcher.rs`)\n   - Watch for file changes\n   - Determine dirty modules\n   - Recompile only affected modules\n\n### Development Guidelines\n\n#### Adding New Features\n\n1. **CLI Arguments**: Add to `cli.rs` in `BuildArgs` and `WatchArgs`\n2. **Configuration**: Extend `config.rs` for new `rescript.json` fields\n3. **Build Logic**: Modify appropriate `build/*.rs` modules\n4. **Thread Parameters**: Pass new parameters through the build system chain\n5. **Add Tests**: Include unit tests for new functionality\n\n#### Common Patterns\n\n- **Parameter Threading**: New CLI flags need to be passed through:\n  - `main.rs` → `build::build()` → `initialize_build()` → `BuildState`\n  - `main.rs` → `watcher::start()` → `async_watch()` → `initialize_build()`\n\n- **Configuration Precedence**: Command-line flags override `rescript.json` config\n- **Error Handling**: Use `anyhow::Result` for error propagation\n- **Logging**: Use `log::debug!` for development debugging\n\n#### Testing\n\n```bash\n# Run rewatch tests (from project root)\ncargo test --manifest-path rewatch/Cargo.toml\n\n# Test specific functionality\ncargo test --manifest-path rewatch/Cargo.toml config::tests::test_get_warning_args\n\n# Run clippy for code quality\ncargo clippy --manifest-path rewatch/Cargo.toml --all-targets --all-features\n\n# Check formatting\ncargo fmt --check --manifest-path rewatch/Cargo.toml\n\n# Build rewatch\ncargo build --manifest-path rewatch/Cargo.toml --release\n\n# Or use the Makefile shortcuts\nmake rewatch          # Build rewatch\nmake test-rewatch     # Run integration tests\n```\n\n**Note**: The rewatch project is located in the `rewatch/` directory with its own `Cargo.toml` file. All cargo commands should be run from the project root using the `--manifest-path rewatch/Cargo.toml` flag, as shown in the CI workflow.\n\n**Integration Tests**: The `make test-rewatch` command runs bash-based integration tests located in `rewatch/tests/suite.sh`. These tests use the `rewatch/testrepo/` directory as a test workspace with various package configurations to verify rewatch's behavior across different scenarios.\n\n**Running Individual Integration Tests**: You can run individual test scripts directly by setting up the environment manually:\n\n```bash\ncd rewatch/tests\nexport REWATCH_EXECUTABLE=\"$(realpath ../target/debug/rescript)\"\neval $(node ./get_bin_paths.js)\nexport RESCRIPT_BSC_EXE\nexport RESCRIPT_RUNTIME\nsource ./utils.sh\nbash ./watch/06-watch-missing-source-folder.sh\n```\n\nThis is useful for iterating on a specific test without running the full suite.\n\n#### Debugging\n\n- **Build State**: Use `log::debug!` to inspect `BuildState` contents\n- **Compiler Args**: Check generated `bsc` arguments in `compile.rs`\n- **Dependencies**: Inspect module dependency graph in `deps.rs`\n- **File Watching**: Monitor file change events in `watcher.rs`\n\n#### OpenTelemetry Tracing\n\nRewatch supports OpenTelemetry (OTEL) tracing for build and watch commands. To visualize traces locally, run a Jaeger all-in-one container:\n\n```bash\ndocker run -d --name jaeger \\\n  -p 4317:4317 -p 4318:4318 -p 16686:16686 \\\n  jaegertracing/all-in-one\n```\n\nThen run rewatch with the OTLP endpoint set:\n\n```bash\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 cargo run --manifest-path rewatch/Cargo.toml -- build\n```\n\nOpen http://localhost:16686 to view traces in the Jaeger UI.\n\nNote: Use `tracing::debug!` (not `log::debug!`) for events you want to appear in OTEL traces — they use separate logging systems.\n\n##### Honored environment variables\n\nRewatch follows the OTEL spec for configuration — no rewatch-specific knobs exist.\n\n| Variable | Purpose |\n|---|---|\n| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base endpoint of the collector (e.g. `http://localhost:4318`). `/v1/traces` is appended for the trace exporter. Setting this (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is what enables telemetry — if neither is set, tracing is a no-op. |\n| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Full trace endpoint used verbatim. Overrides the general endpoint for traces. |\n| `OTEL_EXPORTER_OTLP_HEADERS` | Extra headers on exporter requests (e.g. `authorization=Bearer xyz`). |\n| `OTEL_SERVICE_NAME` | Service name reported on spans. Defaults to `rewatch`. |\n| `OTEL_RESOURCE_ATTRIBUTES` | Comma-separated `key=value` pairs added as resource attributes (e.g. `deployment.environment=ci,host.name=$HOSTNAME`). |\n| `RUST_LOG` | Controls which span/event levels are captured (e.g. `RUST_LOG=info`, `RUST_LOG=rewatch=debug`). Defaults to `debug` when telemetry is enabled. |\n\n#### Running Rewatch Directly\n\nWhen running the rewatch binary directly (via `cargo run` or the compiled binary) during development, you need to set environment variables to point to the local compiler and runtime. Otherwise, rewatch will try to use the installed versions:\n\n```bash\n# Set the compiler executable path\nexport RESCRIPT_BSC_EXE=$(realpath _build/default/compiler/bsc/rescript_compiler_main.exe)\n\n# Set the runtime path\nexport RESCRIPT_RUNTIME=$(realpath packages/@rescript/runtime)\n\n# Now you can run rewatch directly\ncargo run --manifest-path rewatch/Cargo.toml -- build\n```\n\nNote that the dev binary is `./rewatch/target/debug/rescript`, not `rewatch`. The binary name is `rescript` because that's the package name in `Cargo.toml`.\n\nThis is useful when testing rewatch changes against local compiler modifications without running a full `make` build cycle.\n\nUse `-v` for info-level logging or `-vv` for debug-level logging (e.g., to see which folders are being watched in watch mode):\n\n```bash\ncargo run --manifest-path rewatch/Cargo.toml -- -vv watch <folder>\n```\n\n#### Performance Considerations\n\n- **Incremental Builds**: Only recompile dirty modules\n- **Parallel Compilation**: Use `rayon` for parallel processing\n- **Memory Usage**: Be mindful of `BuildState` size in large projects\n- **File I/O**: Minimize file system operations\n\n#### Performance vs Code Quality Trade-offs\n\nWhen clippy suggests refactoring that could impact performance, consider the trade-offs:\n\n- **Parameter Structs vs Many Arguments**: While clippy prefers parameter structs for functions with many arguments, sometimes the added complexity isn't worth it. Use `#[allow(clippy::too_many_arguments)]` for functions that legitimately need many parameters and where a struct would add unnecessary complexity.\n\n- **Cloning vs Borrowing**: Sometimes cloning is necessary due to Rust's borrow checker rules. If the clone is:\n  - Small and one-time (e.g., `Vec<String>` with few elements)\n  - Necessary for correct ownership semantics\n  - Not in a hot path\n\n  Then accept the clone rather than over-engineering the solution.\n\n- **When to Optimize**: Profile before optimizing. Most \"performance concerns\" in build systems are negligible compared to actual compilation time.\n\n- **Avoid Unnecessary Type Conversions**: When threading parameters through multiple function calls, use consistent types (e.g., `String` throughout) rather than converting between `String` and `&str` at each boundary. This eliminates unnecessary allocations and conversions.\n\n### Common Tasks\n\n#### Adding New CLI Flags\n\n1. Add to `BuildArgs` and `WatchArgs` in `cli.rs`\n2. Update `From<BuildArgs> for WatchArgs` implementation\n3. Pass through `main.rs` to build functions\n4. Thread through build system to where it's needed\n5. Add unit tests for the new functionality\n\n#### Modifying Compiler Arguments\n\n1. Update `compiler_args()` in `build/compile.rs`\n2. Consider both parsing and compilation phases\n3. Handle precedence between CLI flags and config\n4. Test with various `rescript.json` configurations\n\n#### Working with Dependencies\n\n1. Use `packages.rs` for package discovery\n2. Update `deps.rs` for dependency analysis\n3. Handle both local and external dependencies\n4. Consider dev dependencies vs regular dependencies\n\n#### File Watching\n\n1. Modify `watcher.rs` for file change handling\n2. Update `AsyncWatchArgs` for new parameters\n3. Handle different file types (`.res`, `.resi`, etc.)\n4. Consider performance impact of watching many files\n\n## CI Gotchas\n\n- **`sleep` is fragile** — Prefer polling (e.g., `wait_for_file`) over fixed sleeps. CI runners are slower than local machines.\n- **`exit_watcher` is async** — It only signals the watcher to stop (removes the lock file), it doesn't wait for the process to exit. Avoid triggering config-change events before exiting, as the watcher may start a concurrent rebuild.\n- **`sed -i` differs across platforms** — macOS requires `sed -i '' ...`, Linux does not. Use the `replace` / `normalize_paths` helpers from `rewatch/tests/utils.sh` instead of raw `sed`.\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI coding assistants when working with code in this repository.\n\n## Quick Start: Essential Commands\n\n```bash\n# Build the platform toolchain (default target)\nmake\n\n# Build the platform toolchain + stdlib\nmake lib\n\n# Build the platform toolchain + stdlib and run tests\nmake test\n\n# Format code\nmake format\n\n# Check formatting\nmake checkformat\n```\n\nThe Makefile’s targets build on each other in this order:\n\n1. `yarn-install` runs automatically for targets that need JavaScript tooling (lib, playground, tests, formatting, etc.).\n2. `build` (default target) builds the toolchain binaries (all copied into `packages/@rescript/<platform>/bin`):\n   - `compiler` builds the dune executables (`bsc`, `rescript-*`, `ounit_tests`, etc.).\n   - `rewatch` builds the Rust-based ReScript build system and CLI.\n3. `lib` uses those toolchain outputs to build the runtime sources.\n4. Test targets (`make test`, `make test-syntax`, etc.) reuse everything above.\n\n## ⚠️ Critical Guidelines & Common Pitfalls\n\n- **We are NOT bound by OCaml compatibility** - The ReScript compiler originated as a fork of the OCaml compiler, but we maintain our own AST and can make breaking changes. Focus on what's best for ReScript's JavaScript compilation target.\n\n- **Never modify `parsetree0.ml`** - Existing PPX (parser extensions) rely on this frozen v0 version. When changing `parsetree.ml`, always update the mapping modules `ast_mapper_from0.ml` and `ast_mapper_to0.ml` to maintain PPX compatibility while allowing the main parsetree to evolve\n\n- **Missing test coverage** - Always add tests for syntax, lambda, and end-to-end behavior\n\n- **Test early and often** - Add tests immediately after modifying each compiler layer to catch problems early, rather than waiting until all changes are complete\n\n- **Use underscore patterns carefully** - Don't use `_` patterns as lazy placeholders for new language features that then get forgotten. Only use them when you're certain the value should be ignored for that specific case. Ensure all new language features are handled correctly and completely across all compiler layers\n- **Avoid `let _ = …` for side effects** - If you need to call a function only for its side effects, use `ignore expr` (or bind the result and thread state explicitly). Do not write `let _ = expr in ()`, and do not discard stateful results—plumb them through instead.\n\n- **Don't use unit `()` with mandatory labeled arguments** - When a function has a mandatory labeled argument (like `~config`), don't add a trailing `()` parameter. The labeled argument already prevents accidental partial application. Only use `()` when all parameters are optional and you need to force evaluation. Example: `let forceDelayedItems ~config = ...` not `let forceDelayedItems ~config () = ...`\n\n- **Be careful with similar constructor names across different IRs** - Note that `Lam` (Lambda IR) and `Lambda` (typed lambda) have variants with similar constructor names like `Ltrywith`, but they represent different things in different compilation phases.\n\n- **Avoid warning suppressions** - Never use `[@@warning \"...\"]` to silence warnings. Instead, fix the underlying issue properly\n- **Skip trailing `; _` in record patterns** - The warning it targets is disabled in this codebase, so prefer `{field = x}` over `{field = x; _}`.\n\n- **Do not introduce new keywords unless absolutely necessary** - Try to find ways to implement features without reserving keywords, as seen with the \"catch\" implementation that avoids making it a keyword.\n\n## Compiler Architecture\n\n### Compilation Pipeline\n\n```\nReScript Source (.res)\n  ↓ (ReScript Parser - compiler/syntax/)\nSurface Syntax Tree\n  ↓ (Frontend transformations - compiler/frontend/)\nSurface Syntax Tree\n  ↓ (OCaml Type Checker - compiler/ml/)\nTypedtree\n  ↓ (Lambda compilation - compiler/core/lam_*)\nLambda IR\n  ↓ (JS compilation - compiler/core/js_*)\nJS IR\n  ↓ (JS output - compiler/core/js_dump*)\nJavaScript Code\n```\n\n### Platform-specific compiler modules\n\nThe Dune `browser` profile builds the playground compiler. Platform-dependent\nmodules are stored below `platform/native/` and `platform/playground/` in their\nowning compiler directory. Rules in that directory's `dune` file copy the\nselected implementation into the build directory as an ordinary `.ml` module;\nall other profiles select the native source. Generated module paths in errors\nor stack traces therefore map back to one of those two source directories.\n\n### Key Directory Structure\n\n```\ncompiler/\n├── syntax/          # ReScript syntax parser (MIT licensed)\n├── frontend/        # AST transformations, FFI processing\n├── ml/              # OCaml compiler infrastructure\n├── core/            # Core compilation (lam_*, js_* files)\n├── ext/             # Extended utilities and data structures\n└── gentype/         # TypeScript generation\n\nanalysis/            # Language server and tooling\npackages/@rescript/\n├── runtime/         # Runtime and standard library\n└── <platform>/      # Platform-specific binaries\n\ntests/\n├── syntax_tests/    # Parser/syntax layer tests\n├── tests/           # Runtime library tests\n├── build_tests/     # Integration tests\n└── ounit_tests/     # Compiler unit tests\n```\n\n## Working on the Compiler\n\n### Development Workflow\n\n1. **Understand which layer you're working on:**\n   - **Syntax layer** (`compiler/syntax/`): Parsing and surface syntax\n   - **ML layer** (`compiler/ml/`): Type checking and AST transformations\n   - **Lambda layer** (`compiler/core/lam_*`): Intermediate representation and optimizations\n   - **JS layer** (`compiler/core/js_*`): JavaScript generation\n\n2. **Always run appropriate tests:**\n\n   ```bash\n   # For compiler or stdlib changes\n   make test\n\n   # For syntax changes\n   make test-syntax\n\n   # For specific test types\n   make test-syntax-roundtrip\n   make test-gentype\n   make test-analysis\n   ```\n\n3. **Test your changes thoroughly:**\n   - Syntax tests for new language features\n   - Integration tests for behavior changes\n   - Unit tests for utility functions\n   - Always check JavaScript output quality\n\n4. **Add a `CHANGELOG.md` entry** for any user-facing change (bug fix, feature, or breaking change). Put it under the matching section of the current `(Unreleased)` version and end the line with the PR link. See [CONTRIBUTING.md](CONTRIBUTING.md). PRs are expected to include one.\n\n### Debugging Techniques\n\n#### View Intermediate Representations\n\n```bash\n# Source code (for debugging preprocessing)\n./cli/bsc.js -dsource myfile.res\n\n# Parse tree (surface syntax after parsing)\n./cli/bsc.js -dparsetree myfile.res\n\n# Typed tree (after type checking)\n./cli/bsc.js -dtypedtree myfile.res\n\n# Raw lambda (unoptimized intermediate representation)\n./cli/bsc.js -drawlambda myfile.res\n\n# Use lambda printing for debugging (add in compiler/core/lam_print.ml)\n```\n\n#### Common Debug Scenarios\n\n- **JavaScript formatting issues**: Check `compiler/ml/pprintast.ml`\n- **Type checking issues**: Look in `compiler/ml/` type checker modules\n- **Optimization bugs**: Check `compiler/core/lam_*.ml` analysis passes\n- **Code generation bugs**: Look in `compiler/core/js_*.ml` modules\n\n### Testing Requirements\n\n#### When to Add Tests\n\n- **Always** for new language features\n- **Always** for bug fixes\n- **When modifying** analysis passes\n- **When changing** JavaScript generation\n\n#### Test Types to Include\n\n1. **Syntax tests** (`tests/syntax_tests/`) - Parser validation\n2. **Integration tests** (`tests/tests/`) - End-to-end behavior\n3. **Unit tests** (`tests/ounit_tests/`) - Compiler functions\n4. **Build tests** (`tests/build_tests/`) - Error cases and edge cases\n5. **Type tests** (`tests/build_tests/super_errors/`) - Single-file type checking errors\n6. **Multi-file error tests** (`tests/build_tests/super_errors_multi/`) - Cross-module errors that need separate `.res` / `.resi` files\n\n#### Error variant catalog\n\n[`tests/ERROR_VARIANTS.md`](tests/ERROR_VARIANTS.md) is a per-module\ncatalog of every error and warning variant the compiler can emit, with\neach entry mapped to a fixture (or a documented reason it's unreachable).\n\n**When adding or removing an error variant**, also update the catalog:\n\n1. Add (or remove) the row in the relevant module section.\n2. Set the status (`✓` covered / `⚠` unreachable / `☐` TODO).\n3. If covered, link the fixture path; if unreachable, note the reason.\n\n**When adding or removing a fixture**, update the corresponding row's\n`Fixture` and status columns so the catalog stays in sync with the test\nsuite. The catalog is the primary tool for finding coverage gaps and\ndead-code removal candidates; stale entries make both jobs harder.\n\n## Build Commands & Development\n\n### Essential Commands\n\n```bash\n# Build compiler\nmake\n\n# Build compiler in watch mode\nmake watch\n\n# Build compiler and standard library\nmake lib\n\n# Build compiler and standard library and run all tests\nmake test\n\n# Build artifacts and update artifact list\nmake artifacts\n\n# Clean build\nmake clean\n```\n\n### Testing Commands\n\n```bash\n# Specific test types\nmake test-syntax           # Syntax parser tests\nmake test-syntax-roundtrip # Roundtrip syntax tests\nmake test-gentype         # GenType tests\nmake test-analysis        # Analysis tests\nmake test-tools           # Tools tests\nmake test-rewatch         # Rewatch tests\n\n# Single file debugging\n./cli/bsc.js myfile.res\n```\n\n### Code Quality\n\n```bash\n# Format code\nmake format\n\n# Check formatting\nmake checkformat\n\n# Lint with Biome\nnpm run check\nnpm run check:all\n\n# TypeScript type checking\nnpm run typecheck\n```\n\n## Performance Considerations\n\nThe compiler is designed for fast feedback loops and scales to large codebases:\n\n- **Avoid meaningless symbols** in generated JavaScript\n- **Maintain readable JavaScript output**\n- **Consider compilation speed impact** of changes\n- **Use appropriate optimization passes** in Lambda and JS IRs\n- **Profile** before and after performance-related changes\n\n## Coding Conventions\n\n### Naming\n\n- **OCaml code**: snake_case (e.g., `to_string`)\n- **ReScript code**: camelCase (e.g., `toString`)\n\n### Commit Standards\n\n- Use DCO sign-off: `Signed-Off-By: Your Name <email>`\n- Include appropriate tests with all changes\n- Build must pass before committing\n\n### Code Quality\n\n- Follow existing patterns in the codebase\n- Prefer existing utility functions over reinventing\n- Comment complex algorithms and non-obvious logic\n- Maintain backward compatibility where possible\n\n## Development Environment\n\n- **OCaml**: 5.3.0+ with opam\n- **Build System**: dune with profiles (dev, release, browser)\n- **JavaScript**: Node.js 20+ for tooling\n- **Rust**: Toolchain needed for rewatch\n\n## Common Tasks\n\n### Adding New Language Features\n\n1. Update parser in `compiler/syntax/`\n2. Update AST definitions in `compiler/ml/`\n3. Implement type checking in `compiler/ml/`\n4. Add Lambda IR handling in `compiler/core/lam_*`\n5. Implement JS generation in `compiler/core/js_*`\n6. Add comprehensive tests\n\n### Debugging Compilation Issues\n\n1. Identify which compilation phase has the issue\n2. Use appropriate debugging flags (`-dparsetree`, `-dtypedtree`)\n3. Check intermediate representations\n4. Add debug output in relevant compiler modules\n5. Verify with minimal test cases\n\n### Working with Lambda IR\n\n- Remember Lambda IR is the core optimization layer\n- All `lam_*.ml` files process this representation\n- Use `lam_print.ml` for debugging lambda expressions\n- Test both with and without optimization passes\n\n## Working on the Build System\n\n### Rewatch Architecture\n\nRewatch is ReScript's build system written in Rust. It provides fast incremental builds, better error messages, and improved developer experience.\n\n#### Key Components\n\n```\nrewatch/src/\n├── build/              # Core build system logic\n│   ├── build_types.rs  # Core data structures (BuildState, Module, etc.)\n│   ├── compile.rs      # Compilation logic and bsc argument generation\n│   ├── parse.rs        # AST generation and parser argument handling\n│   ├── packages.rs     # Package discovery and dependency resolution\n│   ├── deps.rs         # Dependency analysis and module graph\n│   ├── clean.rs        # Build artifact cleanup\n│   └── logs.rs         # Build logging and error reporting\n├── cli.rs              # Command-line interface definitions\n├── config.rs           # rescript.json configuration parsing\n├── watcher.rs          # File watching and incremental builds\n└── main.rs             # Application entry point\n```\n\n#### Build System Flow\n\n1. **Initialization** (`build::initialize_build`)\n   - Parse `rescript.json` configuration\n   - Discover packages and dependencies\n   - Set up compiler information\n   - Create initial `BuildState`\n\n2. **AST Generation** (`build::parse`)\n   - Generate AST files using `bsc -bs-ast`\n   - Handle PPX transformations\n   - Process JSX\n\n3. **Dependency Analysis** (`build::deps`)\n   - Analyze module dependencies from AST files\n   - Build dependency graph\n   - Detect circular dependencies\n\n4. **Compilation** (`build::compile`)\n   - Generate `bsc` compiler arguments\n   - Compile modules in dependency order\n   - Handle warnings and errors\n   - Generate JavaScript output\n\n5. **Incremental Updates** (`watcher.rs`)\n   - Watch for file changes\n   - Determine dirty modules\n   - Recompile only affected modules\n\n### Development Guidelines\n\n#### Adding New Features\n\n1. **CLI Arguments**: Add to `cli.rs` in `BuildArgs` and `WatchArgs`\n2. **Configuration**: Extend `config.rs` for new `rescript.json` fields\n3. **Build Logic**: Modify appropriate `build/*.rs` modules\n4. **Thread Parameters**: Pass new parameters through the build system chain\n5. **Add Tests**: Include unit tests for new functionality\n\n#### Common Patterns\n\n- **Parameter Threading**: New CLI flags need to be passed through:\n  - `main.rs` → `build::build()` → `initialize_build()` → `BuildState`\n  - `main.rs` → `watcher::start()` → `async_watch()` → `initialize_build()`\n\n- **Configuration Precedence**: Command-line flags override `rescript.json` config\n- **Error Handling**: Use `anyhow::Result` for error propagation\n- **Logging**: Use `log::debug!` for development debugging\n\n#### Testing\n\n```bash\n# Run rewatch tests (from project root)\ncargo test --manifest-path rewatch/Cargo.toml\n\n# Test specific functionality\ncargo test --manifest-path rewatch/Cargo.toml config::tests::test_get_warning_args\n\n# Run clippy for code quality\ncargo clippy --manifest-path rewatch/Cargo.toml --all-targets --all-features\n\n# Check formatting\ncargo fmt --check --manifest-path rewatch/Cargo.toml\n\n# Build rewatch\ncargo build --manifest-path rewatch/Cargo.toml --release\n\n# Or use the Makefile shortcuts\nmake rewatch          # Build rewatch\nmake test-rewatch     # Run integration tests\n```\n\n**Note**: The rewatch project is located in the `rewatch/` directory with its own `Cargo.toml` file. All cargo commands should be run from the project root using the `--manifest-path rewatch/Cargo.toml` flag, as shown in the CI workflow.\n\n**Integration Tests**: The `make test-rewatch` command runs bash-based integration tests located in `rewatch/tests/suite.sh`. These tests use the `rewatch/testrepo/` directory as a test workspace with various package configurations to verify rewatch's behavior across different scenarios.\n\n**Running Individual Integration Tests**: You can run individual test scripts directly by setting up the environment manually:\n\n```bash\ncd rewatch/tests\nexport REWATCH_EXECUTABLE=\"$(realpath ../target/debug/rescript)\"\neval $(node ./get_bin_paths.js)\nexport RESCRIPT_BSC_EXE\nexport RESCRIPT_RUNTIME\nsource ./utils.sh\nbash ./watch/06-watch-missing-source-folder.sh\n```\n\nThis is useful for iterating on a specific test without running the full suite.\n\n#### Debugging\n\n- **Build State**: Use `log::debug!` to inspect `BuildState` contents\n- **Compiler Args**: Check generated `bsc` arguments in `compile.rs`\n- **Dependencies**: Inspect module dependency graph in `deps.rs`\n- **File Watching**: Monitor file change events in `watcher.rs`\n\n#### OpenTelemetry Tracing\n\nRewatch supports OpenTelemetry (OTEL) tracing for build and watch commands. To visualize traces locally, run a Jaeger all-in-one container:\n\n```bash\ndocker run -d --name jaeger \\\n  -p 4317:4317 -p 4318:4318 -p 16686:16686 \\\n  jaegertracing/all-in-one\n```\n\nThen run rewatch with the OTLP endpoint set:\n\n```bash\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 cargo run --manifest-path rewatch/Cargo.toml -- build\n```\n\nOpen http://localhost:16686 to view traces in the Jaeger UI.\n\nNote: Use `tracing::debug!` (not `log::debug!`) for events you want to appear in OTEL traces — they use separate logging systems.\n\n##### Honored environment variables\n\nRewatch follows the OTEL spec for configuration — no rewatch-specific knobs exist.\n\n| Variable | Purpose |\n|---|---|\n| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base endpoint of the collector (e.g. `http://localhost:4318`). `/v1/traces` is appended for the trace exporter. Setting this (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is what enables telemetry — if neither is set, tracing is a no-op. |\n| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Full trace endpoint used verbatim. Overrides the general endpoint for traces. |\n| `OTEL_EXPORTER_OTLP_HEADERS` | Extra headers on exporter requests (e.g. `authorization=Bearer xyz`). |\n| `OTEL_SERVICE_NAME` | Service name reported on spans. Defaults to `rewatch`. |\n| `OTEL_RESOURCE_ATTRIBUTES` | Comma-separated `key=value` pairs added as resource attributes (e.g. `deployment.environment=ci,host.name=$HOSTNAME`). |\n| `RUST_LOG` | Controls which span/event levels are captured (e.g. `RUST_LOG=info`, `RUST_LOG=rewatch=debug`). Defaults to `debug` when telemetry is enabled. |\n\n#### Running Rewatch Directly\n\nWhen running the rewatch binary directly (via `cargo run` or the compiled binary) during development, you need to set environment variables to point to the local compiler and runtime. Otherwise, rewatch will try to use the installed versions:\n\n```bash\n# Set the compiler executable path\nexport RESCRIPT_BSC_EXE=$(realpath _build/default/compiler/bsc/rescript_compiler_main.exe)\n\n# Set the runtime path\nexport RESCRIPT_RUNTIME=$(realpath packages/@rescript/runtime)\n\n# Now you can run rewatch directly\ncargo run --manifest-path rewatch/Cargo.toml -- build\n```\n\nNote that the dev binary is `./rewatch/target/debug/rescript`, not `rewatch`. The binary name is `rescript` because that's the package name in `Cargo.toml`.\n\nThis is useful when testing rewatch changes against local compiler modifications without running a full `make` build cycle.\n\nUse `-v` for info-level logging or `-vv` for debug-level logging (e.g., to see which folders are being watched in watch mode):\n\n```bash\ncargo run --manifest-path rewatch/Cargo.toml -- -vv watch <folder>\n```\n\n#### Performance Considerations\n\n- **Incremental Builds**: Only recompile dirty modules\n- **Parallel Compilation**: Use `rayon` for parallel processing\n- **Memory Usage**: Be mindful of `BuildState` size in large projects\n- **File I/O**: Minimize file system operations\n\n#### Performance vs Code Quality Trade-offs\n\nWhen clippy suggests refactoring that could impact performance, consider the trade-offs:\n\n- **Parameter Structs vs Many Arguments**: While clippy prefers parameter structs for functions with many arguments, sometimes the added complexity isn't worth it. Use `#[allow(clippy::too_many_arguments)]` for functions that legitimately need many parameters and where a struct would add unnecessary complexity.\n\n- **Cloning vs Borrowing**: Sometimes cloning is necessary due to Rust's borrow checker rules. If the clone is:\n  - Small and one-time (e.g., `Vec<String>` with few elements)\n  - Necessary for correct ownership semantics\n  - Not in a hot path\n\n  Then accept the clone rather than over-engineering the solution.\n\n- **When to Optimize**: Profile before optimizing. Most \"performance concerns\" in build systems are negligible compared to actual compilation time.\n\n- **Avoid Unnecessary Type Conversions**: When threading parameters through multiple function calls, use consistent types (e.g., `String` throughout) rather than converting between `String` and `&str` at each boundary. This eliminates unnecessary allocations and conversions.\n\n### Common Tasks\n\n#### Adding New CLI Flags\n\n1. Add to `BuildArgs` and `WatchArgs` in `cli.rs`\n2. Update `From<BuildArgs> for WatchArgs` implementation\n3. Pass through `main.rs` to build functions\n4. Thread through build system to where it's needed\n5. Add unit tests for the new functionality\n\n#### Modifying Compiler Arguments\n\n1. Update `compiler_args()` in `build/compile.rs`\n2. Consider both parsing and compilation phases\n3. Handle precedence between CLI flags and config\n4. Test with various `rescript.json` configurations\n\n#### Working with Dependencies\n\n1. Use `packages.rs` for package discovery\n2. Update `deps.rs` for dependency analysis\n3. Handle both local and external dependencies\n4. Consider dev dependencies vs regular dependencies\n\n#### File Watching\n\n1. Modify `watcher.rs` for file change handling\n2. Update `AsyncWatchArgs` for new parameters\n3. Handle different file types (`.res`, `.resi`, etc.)\n4. Consider performance impact of watching many files\n\n## CI Gotchas\n\n- **`sleep` is fragile** — Prefer polling (e.g., `wait_for_file`) over fixed sleeps. CI runners are slower than local machines.\n- **`exit_watcher` is async** — It only signals the watcher to stop (removes the lock file), it doesn't wait for the process to exit. Avoid triggering config-change events before exiting, as the watcher may start a concurrent rebuild.\n- **`sed -i` differs across platforms** — macOS requires `sed -i '' ...`, Linux does not. Use the `replace` / `normalize_paths` helpers from `rewatch/tests/utils.sh` instead of raw `sed`.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nThis file provides guidance to AI coding assistants when working with code in this repository.\n\n## Quick Start: Essential Commands\n\n```bash\n# Build the platform toolchain (default target)\nmake\n\n# Build the platform toolchain + stdlib\nmake lib\n\n# Build the platform toolchain + stdlib and run tests\nmake test\n\n# Format code\nmake format\n\n# Check formatting\nmake checkformat\n```\n\nThe Makefile’s targets build on each other in this order:\n\n1. `yarn-install` runs automatically for targets that need JavaScript tooling (lib, playground, tests, formatting, etc.).\n2. `build` (default target) builds the toolchain binaries (all copied into `packages/@rescript/<platform>/bin`):\n   - `compiler` builds the dune executables (`bsc`, `rescript-*`, `ounit_tests`, etc.).\n   - `rewatch` builds the Rust-based ReScript build system and CLI.\n3. `lib` uses those toolchain outputs to build the runtime sources.\n4. Test targets (`make test`, `make test-syntax`, etc.) reuse everything above.\n\n## ⚠️ Critical Guidelines & Common Pitfalls\n\n- **We are NOT bound by OCaml compatibility** - The ReScript compiler originated as a fork of the OCaml compiler, but we maintain our own AST and can make breaking changes. Focus on what's best for ReScript's JavaScript compilation target.\n\n- **Never modify `parsetree0.ml`** - Existing PPX (parser extensions) rely on this frozen v0 version. When changing `parsetree.ml`, always update the mapping modules `ast_mapper_from0.ml` and `ast_mapper_to0.ml` to maintain PPX compatibility while allowing the main parsetree to evolve\n\n- **Missing test coverage** - Always add tests for syntax, lambda, and end-to-end behavior\n\n- **Test early and often** - Add tests immediately after modifying each compiler layer to catch problems early, rather than waiting until all changes are complete\n\n- **Use underscore patterns carefully** - Don't use `_` patterns as lazy placeholders for new language features that then get forgotten. Only use them when you're certain the value should be ignored for that specific case. Ensure all new language features are handled correctly and completely across all compiler layers\n- **Avoid `let _ = …` for side effects** - If you need to call a function only for its side effects, use `ignore expr` (or bind the result and thread state explicitly). Do not write `let _ = expr in ()`, and do not discard stateful results—plumb them through instead.\n\n- **Don't use unit `()` with mandatory labeled arguments** - When a function has a mandatory labeled argument (like `~config`), don't add a trailing `()` parameter. The labeled argument already prevents accidental partial application. Only use `()` when all parameters are optional and you need to force evaluation. Example: `let forceDelayedItems ~config = ...` not `let forceDelayedItems ~config () = ...`\n\n- **Be careful with similar constructor names across different IRs** - Note that `Lam` (Lambda IR) and `Lambda` (typed lambda) have variants with similar constructor names like `Ltrywith`, but they represent different things in different compilation phases.\n\n- **Avoid warning suppressions** - Never use `[@@warning \"...\"]` to silence warnings. Instead, fix the underlying issue properly\n- **Skip trailing `; _` in record patterns** - The warning it targets is disabled in this codebase, so prefer `{field = x}` over `{field = x; _}`.\n\n- **Do not introduce new keywords unless absolutely necessary** - Try to find ways to implement features without reserving keywords, as seen with the \"catch\" implementation that avoids making it a keyword.\n\n## Compiler Architecture\n\n### Compilation Pipeline\n\n```\nReScript Source (.res)\n  ↓ (ReScript Parser - compiler/syntax/)\nSurface Syntax Tree\n  ↓ (Frontend transformations - compiler/frontend/)\nSurface Syntax Tree\n  ↓ (OCaml Type Checker - compiler/ml/)\nTypedtree\n  ↓ (Lambda compilation - compiler/core/lam_*)\nLambda IR\n  ↓ (JS compilation - compiler/core/js_*)\nJS IR\n  ↓ (JS output - compiler/core/js_dump*)\nJavaScript Code\n```\n\n### Platform-specific compiler modules\n\nThe Dune `browser` profile builds the playground compiler. Platform-dependent\nmodules are stored below `platform/native/` and `platform/playground/` in their\nowning compiler directory. Rules in that directory's `dune` file copy the\nselected implementation into the build directory as an ordinary `.ml` module;\nall other profiles select the native source. Generated module paths in errors\nor stack traces therefore map back to one of those two source directories.\n\n### Key Directory Structure\n\n```\ncompiler/\n├── syntax/          # ReScript syntax parser (MIT licensed)\n├── frontend/        # AST transformations, FFI processing\n├── ml/              # OCaml compiler infrastructure\n├── core/            # Core compilation (lam_*, js_* files)\n├── ext/             # Extended utilities and data structures\n└── gentype/         # TypeScript generation\n\nanalysis/            # Language server and tooling\npackages/@rescript/\n├── runtime/         # Runtime and standard library\n└── <platform>/      # Platform-specific binaries\n\ntests/\n├── syntax_tests/    # Parser/syntax layer tests\n├── tests/           # Runtime library tests\n├── build_tests/     # Integration tests\n└── ounit_tests/     # Compiler unit tests\n```\n\n## Working on the Compiler\n\n### Development Workflow\n\n1. **Understand which layer you're working on:**\n   - **Syntax layer** (`compiler/syntax/`): Parsing and surface syntax\n   - **ML layer** (`compiler/ml/`): Type checking and AST transformations\n   - **Lambda layer** (`compiler/core/lam_*`): Intermediate representation and optimizations\n   - **JS layer** (`compiler/core/js_*`): JavaScript generation\n\n2. **Always run appropriate tests:**\n\n   ```bash\n   # For compiler or stdlib changes\n   make test\n\n   # For syntax changes\n   make test-syntax\n\n   # For specific test types\n   make test-syntax-roundtrip\n   make test-gentype\n   make test-analysis\n   ```\n\n3. **Test your changes thoroughly:**\n   - Syntax tests for new language features\n   - Integration tests for behavior changes\n   - Unit tests for utility functions\n   - Always check JavaScript output quality\n\n4. **Add a `CHANGELOG.md` entry** for any user-facing change (bug fix, feature, or breaking change). Put it under the matching section of the current `(Unreleased)` version and end the line with the PR link. See [CONTRIBUTING.md](CONTRIBUTING.md). PRs are expected to include one.\n\n### Debugging Techniques\n\n#### View Intermediate Representations\n\n```bash\n# Source code (for debugging preprocessing)\n./cli/bsc.js -dsource myfile.res\n\n# Parse tree (surface syntax after parsing)\n./cli/bsc.js -dparsetree myfile.res\n\n# Typed tree (after type checking)\n./cli/bsc.js -dtypedtree myfile.res\n\n# Raw lambda (unoptimized intermediate representation)\n./cli/bsc.js -drawlambda myfile.res\n\n# Use lambda printing for debugging (add in compiler/core/lam_print.ml)\n```\n\n#### Common Debug Scenarios\n\n- **JavaScript formatting issues**: Check `compiler/ml/pprintast.ml`\n- **Type checking issues**: Look in `compiler/ml/` type checker modules\n- **Optimization bugs**: Check `compiler/core/lam_*.ml` analysis passes\n- **Code generation bugs**: Look in `compiler/core/js_*.ml` modules\n\n### Testing Requirements\n\n#### When to Add Tests\n\n- **Always** for new language features\n- **Always** for bug fixes\n- **When modifying** analysis passes\n- **When changing** JavaScript generation\n\n#### Test Types to Include\n\n1. **Syntax tests** (`tests/syntax_tests/`) - Parser validation\n2. **Integration tests** (`tests/tests/`) - End-to-end behavior\n3. **Unit tests** (`tests/ounit_tests/`) - Compiler functions\n4. **Build tests** (`tests/build_tests/`) - Error cases and edge cases\n5. **Type tests** (`tests/build_tests/super_errors/`) - Single-file type checking errors\n6. **Multi-file error tests** (`tests/build_tests/super_errors_multi/`) - Cross-module errors that need separate `.res` / `.resi` files\n\n#### Error variant catalog\n\n[`tests/ERROR_VARIANTS.md`](tests/ERROR_VARIANTS.md) is a per-module\ncatalog of every error and warning variant the compiler can emit, with\neach entry mapped to a fixture (or a documented reason it's unreachable).\n\n**When adding or removing an error variant**, also update the catalog:\n\n1. Add (or remove) the row in the relevant module section.\n2. Set the status (`✓` covered / `⚠` unreachable / `☐` TODO).\n3. If covered, link the fixture path; if unreachable, note the reason.\n\n**When adding or removing a fixture**, update the corresponding row's\n`Fixture` and status columns so the catalog stays in sync with the test\nsuite. The catalog is the primary tool for finding coverage gaps and\ndead-code removal candidates; stale entries make both jobs harder.\n\n## Build Commands & Development\n\n### Essential Commands\n\n```bash\n# Build compiler\nmake\n\n# Build compiler in watch mode\nmake watch\n\n# Build compiler and standard library\nmake lib\n\n# Build compiler and standard library and run all tests\nmake test\n\n# Build artifacts and update artifact list\nmake artifacts\n\n# Clean build\nmake clean\n```\n\n### Testing Commands\n\n```bash\n# Specific test types\nmake test-syntax           # Syntax parser tests\nmake test-syntax-roundtrip # Roundtrip syntax tests\nmake test-gentype         # GenType tests\nmake test-analysis        # Analysis tests\nmake test-tools           # Tools tests\nmake test-rewatch         # Rewatch tests\n\n# Single file debugging\n./cli/bsc.js myfile.res\n```\n\n### Code Quality\n\n```bash\n# Format code\nmake format\n\n# Check formatting\nmake checkformat\n\n# Lint with Biome\nnpm run check\nnpm run check:all\n\n# TypeScript type checking\nnpm run typecheck\n```\n\n## Performance Considerations\n\nThe compiler is designed for fast feedback loops and scales to large codebases:\n\n- **Avoid meaningless symbols** in generated JavaScript\n- **Maintain readable JavaScript output**\n- **Consider compilation speed impact** of changes\n- **Use appropriate optimization passes** in Lambda and JS IRs\n- **Profile** before and after performance-related changes\n\n## Coding Conventions\n\n### Naming\n\n- **OCaml code**: snake_case (e.g., `to_string`)\n- **ReScript code**: camelCase (e.g., `toString`)\n\n### Commit Standards\n\n- Use DCO sign-off: `Signed-Off-By: Your Name <email>`\n- Include appropriate tests with all changes\n- Build must pass before committing\n\n### Code Quality\n\n- Follow existing patterns in the codebase\n- Prefer existing utility functions over reinventing\n- Comment complex algorithms and non-obvious logic\n- Maintain backward compatibility where possible\n\n## Development Environment\n\n- **OCaml**: 5.3.0+ with opam\n- **Build System**: dune with profiles (dev, release, browser)\n- **JavaScript**: Node.js 20+ for tooling\n- **Rust**: Toolchain needed for rewatch\n\n## Common Tasks\n\n### Adding New Language Features\n\n1. Update parser in `compiler/syntax/`\n2. Update AST definitions in `compiler/ml/`\n3. Implement type checking in `compiler/ml/`\n4. Add Lambda IR handling in `compiler/core/lam_*`\n5. Implement JS generation in `compiler/core/js_*`\n6. Add comprehensive tests\n\n### Debugging Compilation Issues\n\n1. Identify which compilation phase has the issue\n2. Use appropriate debugging flags (`-dparsetree`, `-dtypedtree`)\n3. Check intermediate representations\n4. Add debug output in relevant compiler modules\n5. Verify with minimal test cases\n\n### Working with Lambda IR\n\n- Remember Lambda IR is the core optimization layer\n- All `lam_*.ml` files process this representation\n- Use `lam_print.ml` for debugging lambda expressions\n- Test both with and without optimization passes\n\n## Working on the Build System\n\n### Rewatch Architecture\n\nRewatch is ReScript's build system written in Rust. It provides fast incremental builds, better error messages, and improved developer experience.\n\n#### Key Components\n\n```\nrewatch/src/\n├── build/              # Core build system logic\n│   ├── build_types.rs  # Core data structures (BuildState, Module, etc.)\n│   ├── compile.rs      # Compilation logic and bsc argument generation\n│   ├── parse.rs        # AST generation and parser argument handling\n│   ├── packages.rs     # Package discovery and dependency resolution\n│   ├── deps.rs         # Dependency analysis and module graph\n│   ├── clean.rs        # Build artifact cleanup\n│   └── logs.rs         # Build logging and error reporting\n├── cli.rs              # Command-line interface definitions\n├── config.rs           # rescript.json configuration parsing\n├── watcher.rs          # File watching and incremental builds\n└── main.rs             # Application entry point\n```\n\n#### Build System Flow\n\n1. **Initialization** (`build::initialize_build`)\n   - Parse `rescript.json` configuration\n   - Discover packages and dependencies\n   - Set up compiler information\n   - Create initial `BuildState`\n\n2. **AST Generation** (`build::parse`)\n   - Generate AST files using `bsc -bs-ast`\n   - Handle PPX transformations\n   - Process JSX\n\n3. **Dependency Analysis** (`build::deps`)\n   - Analyze module dependencies from AST files\n   - Build dependency graph\n   - Detect circular dependencies\n\n4. **Compilation** (`build::compile`)\n   - Generate `bsc` compiler arguments\n   - Compile modules in dependency order\n   - Handle warnings and errors\n   - Generate JavaScript output\n\n5. **Incremental Updates** (`watcher.rs`)\n   - Watch for file changes\n   - Determine dirty modules\n   - Recompile only affected modules\n\n### Development Guidelines\n\n#### Adding New Features\n\n1. **CLI Arguments**: Add to `cli.rs` in `BuildArgs` and `WatchArgs`\n2. **Configuration**: Extend `config.rs` for new `rescript.json` fields\n3. **Build Logic**: Modify appropriate `build/*.rs` modules\n4. **Thread Parameters**: Pass new parameters through the build system chain\n5. **Add Tests**: Include unit tests for new functionality\n\n#### Common Patterns\n\n- **Parameter Threading**: New CLI flags need to be passed through:\n  - `main.rs` → `build::build()` → `initialize_build()` → `BuildState`\n  - `main.rs` → `watcher::start()` → `async_watch()` → `initialize_build()`\n\n- **Configuration Precedence**: Command-line flags override `rescript.json` config\n- **Error Handling**: Use `anyhow::Result` for error propagation\n- **Logging**: Use `log::debug!` for development debugging\n\n#### Testing\n\n```bash\n# Run rewatch tests (from project root)\ncargo test --manifest-path rewatch/Cargo.toml\n\n# Test specific functionality\ncargo test --manifest-path rewatch/Cargo.toml config::tests::test_get_warning_args\n\n# Run clippy for code quality\ncargo clippy --manifest-path rewatch/Cargo.toml --all-targets --all-features\n\n# Check formatting\ncargo fmt --check --manifest-path rewatch/Cargo.toml\n\n# Build rewatch\ncargo build --manifest-path rewatch/Cargo.toml --release\n\n# Or use the Makefile shortcuts\nmake rewatch          # Build rewatch\nmake test-rewatch     # Run integration tests\n```\n\n**Note**: The rewatch project is located in the `rewatch/` directory with its own `Cargo.toml` file. All cargo commands should be run from the project root using the `--manifest-path rewatch/Cargo.toml` flag, as shown in the CI workflow.\n\n**Integration Tests**: The `make test-rewatch` command runs bash-based integration tests located in `rewatch/tests/suite.sh`. These tests use the `rewatch/testrepo/` directory as a test workspace with various package configurations to verify rewatch's behavior across different scenarios.\n\n**Running Individual Integration Tests**: You can run individual test scripts directly by setting up the environment manually:\n\n```bash\ncd rewatch/tests\nexport REWATCH_EXECUTABLE=\"$(realpath ../target/debug/rescript)\"\neval $(node ./get_bin_paths.js)\nexport RESCRIPT_BSC_EXE\nexport RESCRIPT_RUNTIME\nsource ./utils.sh\nbash ./watch/06-watch-missing-source-folder.sh\n```\n\nThis is useful for iterating on a specific test without running the full suite.\n\n#### Debugging\n\n- **Build State**: Use `log::debug!` to inspect `BuildState` contents\n- **Compiler Args**: Check generated `bsc` arguments in `compile.rs`\n- **Dependencies**: Inspect module dependency graph in `deps.rs`\n- **File Watching**: Monitor file change events in `watcher.rs`\n\n#### OpenTelemetry Tracing\n\nRewatch supports OpenTelemetry (OTEL) tracing for build and watch commands. To visualize traces locally, run a Jaeger all-in-one container:\n\n```bash\ndocker run -d --name jaeger \\\n  -p 4317:4317 -p 4318:4318 -p 16686:16686 \\\n  jaegertracing/all-in-one\n```\n\nThen run rewatch with the OTLP endpoint set:\n\n```bash\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 cargo run --manifest-path rewatch/Cargo.toml -- build\n```\n\nOpen http://localhost:16686 to view traces in the Jaeger UI.\n\nNote: Use `tracing::debug!` (not `log::debug!`) for events you want to appear in OTEL traces — they use separate logging systems.\n\n##### Honored environment variables\n\nRewatch follows the OTEL spec for configuration — no rewatch-specific knobs exist.\n\n| Variable | Purpose |\n|---|---|\n| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base endpoint of the collector (e.g. `http://localhost:4318`). `/v1/traces` is appended for the trace exporter. Setting this (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is what enables telemetry — if neither is set, tracing is a no-op. |\n| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Full trace endpoint used verbatim. Overrides the general endpoint for traces. |\n| `OTEL_EXPORTER_OTLP_HEADERS` | Extra headers on exporter requests (e.g. `authorization=Bearer xyz`). |\n| `OTEL_SERVICE_NAME` | Service name reported on spans. Defaults to `rewatch`. |\n| `OTEL_RESOURCE_ATTRIBUTES` | Comma-separated `key=value` pairs added as resource attributes (e.g. `deployment.environment=ci,host.name=$HOSTNAME`). |\n| `RUST_LOG` | Controls which span/event levels are captured (e.g. `RUST_LOG=info`, `RUST_LOG=rewatch=debug`). Defaults to `debug` when telemetry is enabled. |\n\n#### Running Rewatch Directly\n\nWhen running the rewatch binary directly (via `cargo run` or the compiled binary) during development, you need to set environment variables to point to the local compiler and runtime. Otherwise, rewatch will try to use the installed versions:\n\n```bash\n# Set the compiler executable path\nexport RESCRIPT_BSC_EXE=$(realpath _build/default/compiler/bsc/rescript_compiler_main.exe)\n\n# Set the runtime path\nexport RESCRIPT_RUNTIME=$(realpath packages/@rescript/runtime)\n\n# Now you can run rewatch directly\ncargo run --manifest-path rewatch/Cargo.toml -- build\n```\n\nNote that the dev binary is `./rewatch/target/debug/rescript`, not `rewatch`. The binary name is `rescript` because that's the package name in `Cargo.toml`.\n\nThis is useful when testing rewatch changes against local compiler modifications without running a full `make` build cycle.\n\nUse `-v` for info-level logging or `-vv` for debug-level logging (e.g., to see which folders are being watched in watch mode):\n\n```bash\ncargo run --manifest-path rewatch/Cargo.toml -- -vv watch <folder>\n```\n\n#### Performance Considerations\n\n- **Incremental Builds**: Only recompile dirty modules\n- **Parallel Compilation**: Use `rayon` for parallel processing\n- **Memory Usage**: Be mindful of `BuildState` size in large projects\n- **File I/O**: Minimize file system operations\n\n#### Performance vs Code Quality Trade-offs\n\nWhen clippy suggests refactoring that could impact performance, consider the trade-offs:\n\n- **Parameter Structs vs Many Arguments**: While clippy prefers parameter structs for functions with many arguments, sometimes the added complexity isn't worth it. Use `#[allow(clippy::too_many_arguments)]` for functions that legitimately need many parameters and where a struct would add unnecessary complexity.\n\n- **Cloning vs Borrowing**: Sometimes cloning is necessary due to Rust's borrow checker rules. If the clone is:\n  - Small and one-time (e.g., `Vec<String>` with few elements)\n  - Necessary for correct ownership semantics\n  - Not in a hot path\n\n  Then accept the clone rather than over-engineering the solution.\n\n- **When to Optimize**: Profile before optimizing. Most \"performance concerns\" in build systems are negligible compared to actual compilation time.\n\n- **Avoid Unnecessary Type Conversions**: When threading parameters through multiple function calls, use consistent types (e.g., `String` throughout) rather than converting between `String` and `&str` at each boundary. This eliminates unnecessary allocations and conversions.\n\n### Common Tasks\n\n#### Adding New CLI Flags\n\n1. Add to `BuildArgs` and `WatchArgs` in `cli.rs`\n2. Update `From<BuildArgs> for WatchArgs` implementation\n3. Pass through `main.rs` to build functions\n4. Thread through build system to where it's needed\n5. Add unit tests for the new functionality\n\n#### Modifying Compiler Arguments\n\n1. Update `compiler_args()` in `build/compile.rs`\n2. Consider both parsing and compilation phases\n3. Handle precedence between CLI flags and config\n4. Test with various `rescript.json` configurations\n\n#### Working with Dependencies\n\n1. Use `packages.rs` for package discovery\n2. Update `deps.rs` for dependency analysis\n3. Handle both local and external dependencies\n4. Consider dev dependencies vs regular dependencies\n\n#### File Watching\n\n1. Modify `watcher.rs` for file change handling\n2. Update `AsyncWatchArgs` for new parameters\n3. Handle different file types (`.res`, `.resi`, etc.)\n4. Consider performance impact of watching many files\n\n## CI Gotchas\n\n- **`sleep` is fragile** — Prefer polling (e.g., `wait_for_file`) over fixed sleeps. CI runners are slower than local machines.\n- **`exit_watcher` is async** — It only signals the watcher to stop (removes the lock file), it doesn't wait for the process to exit. Avoid triggering config-change events before exiting, as the watcher may start a concurrent rebuild.\n- **`sed -i` differs across platforms** — macOS requires `sed -i '' ...`, Linux does not. Use the `replace` / `normalize_paths` helpers from `rewatch/tests/utils.sh` instead of raw `sed`.\n","category":"root","tokens":5444}]}