diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000000..c71d4c907b --- /dev/null +++ b/.mcp.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "ocamllsp": { + "type": "stdio", + "command": "mcp-language-server", + "args": [ + "--workspace", + "/Users/shulhi/Dev/rescript/rescript-compiler", + "--lsp", + "ocamllsp" + ], + "env": {} + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index a4433e20ec..a16a527114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - Don't produce duplicate type definitions for recursive types on hover. https://github.com/rescript-lang/rescript/pull/7524 - Prop punning when types don't match results in I/O error: _none_: No such file or directory. https://github.com/rescript-lang/rescript/pull/7533 - Fix partial application with user-defined function types. https://github.com/rescript-lang/rescript/pull/7548 +- Fix doc comment before variant throwing syntax error. https://github.com/rescript-lang/rescript/pull/7535 #### :nail_care: Polish diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..937ed9585d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,139 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is the ReScript compiler repository - a robustly typed language that compiles to efficient and human-readable JavaScript. ReScript is built using OCaml and includes a complete toolchain with compiler, build system, syntax parser, and standard library. + +## Build Commands + +### Basic Development +```bash +# Build compiler and copy executables +make + +# Build in watch mode +make watch + +# Build everything including standard library +make lib + +# Build artifacts and update artifact list +make artifacts +``` + +### Testing +```bash +# Run all tests +make test + +# Run specific test types +make test-syntax # Syntax parser tests +make test-syntax-roundtrip # Roundtrip syntax tests +make test-gentype # GenType tests +make test-analysis # Analysis tests +make test-tools # Tools tests +make test-rewatch # Rewatch tests + +# Run single file test +./cli/bsc.js myTestFile.res + +# View parse/typed trees for debugging +./cli/bsc.js -dparsetree myTestFile.res +./cli/bsc.js -dtypedtree myTestFile.res +``` + +### Code Quality +```bash +# Format code +make format + +# Check formatting +make checkformat + +# Lint with Biome +npm run check +npm run check:all + +# TypeScript type checking +npm run typecheck +``` + +### Clean Operations +```bash +make clean # Clean OCaml build artifacts +make clean-all # Clean everything including Rust/gentype +``` + +## Compiler Architecture + +The ReScript compiler follows this high-level pipeline: + +``` +ReScript Source (.res) + ↓ (ReScript Parser - compiler/syntax/) +Surface Syntax Tree + ↓ (Frontend transformations - compiler/frontend/) +Surface Syntax Tree + ↓ (OCaml Type Checker - compiler/ml/) +Typedtree + ↓ (Lambda compilation - compiler/core/lam_*) +Lambda IR + ↓ (JS compilation - compiler/core/js_*) +JS IR + ↓ (JS output - compiler/core/js_dump*) +JavaScript Code +``` + +### Key Directories + +- **`compiler/syntax/`** - ReScript syntax parser (MIT licensed, separate from main LGPL) +- **`compiler/frontend/`** - AST transformations, external FFI processing, built-in attributes +- **`compiler/ml/`** - OCaml compiler infrastructure (type checker, typedtree, etc.) +- **`compiler/core/`** - Core compilation: + - `lam_*` files: Lambda IR compilation and optimization passes + - `js_*` files: JavaScript IR and code generation +- **`compiler/ext/`** - Extended utilities and data structures +- **`compiler/bsb/`** - Build system implementation +- **`compiler/gentype/`** - TypeScript generation +- **`runtime/`** - ReScript standard library (written in ReScript) +- **`lib/`** - Compiled JavaScript output of standard library +- **`analysis/`** - Language server and tooling support + +### Build System Components + +- **`compiler/bsb_exe/`** - Main ReScript build tool entry point +- **`compiler/bsc/`** - Compiler binary entry point +- **`rewatch/`** - File watcher (written in Rust) +- **`ninja/`** - Vendored Ninja build system + +## Development Setup Notes + +- Uses OCaml 5.3.0+ with opam for compiler development +- Uses dune as build system with specific profiles (dev, release, browser) +- Node.js 20+ required for JavaScript tooling +- Rust toolchain needed for rewatch file watcher +- Python ≤3.11 required for building ninja + +## Coding Conventions + +- **OCaml code**: snake_case (e.g., `to_string`) +- **ReScript code**: camelCase (e.g., `toString`) +- Use DCO sign-off for all commits: `Signed-Off-By: Your Name <email>` + +## Testing Strategy + +- **Mocha tests** (`tests/tests/`) - Runtime library unit tests +- **Build system tests** (`tests/build_tests/`) - Integration tests +- **OUnit tests** (`tests/ounit_tests/`) - Compiler unit tests +- **Expectation tests** - Plain `.res` files that check compilation output +- Always include appropriate tests with new features/changes + +## Performance Notes + +The compiler is designed for fast feedback loops and scales to large codebases. When making changes: +- Avoid introducing meaningless symbols +- Maintain readable JavaScript output +- Consider compilation speed impact +- Use appropriate optimization passes in the Lambda and JS IRs \ No newline at end of file diff --git a/Example.res b/Example.res new file mode 100644 index 0000000000..b05091f28a --- /dev/null +++ b/Example.res @@ -0,0 +1,10 @@ +type x = + /** first group */ + | A + | B + | C + + /** second group */ + | D + | E + | F diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index ef4782192d..63ca9878c1 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -4886,12 +4886,35 @@ and parse_constr_decl_args p = * | constr-name const-args * | attrs constr-name const-args *) and parse_type_constructor_declaration_with_bar p = - match p.Parser.token with - | Bar -> + let is_constructor_with_bar p = + Parser.lookahead p (fun state -> + match state.Parser.token with + | DocComment _ -> ( + Parser.next state; + match state.token with + | Bar -> true + | _ -> false) + | Bar -> true + | _ -> false) + in + if is_constructor_with_bar p then ( + let doc_comment_attrs = + match p.Parser.token with + | DocComment (loc, s) -> + Parser.next p; + [doc_comment_to_attribute loc s] + | _ -> [] + in let start_pos = p.Parser.start_pos in Parser.next p; - Some (parse_type_constructor_declaration ~start_pos p) - | _ -> None + let constr = parse_type_constructor_declaration ~start_pos p in + Some + { + constr with + Parsetree.pcd_attributes = + doc_comment_attrs @ constr.Parsetree.pcd_attributes; + }) + else None and parse_type_constructor_declaration ~start_pos p = Parser.leave_breadcrumb p Grammar.ConstructorDeclaration; @@ -4920,9 +4943,17 @@ and parse_type_constructor_declarations ?first p = let first_constr_decl = match first with | None -> + let doc_comment_attrs = + match p.Parser.token with + | DocComment (loc, s) -> + Parser.next p; + [doc_comment_to_attribute loc s] + | _ -> [] + in let start_pos = p.Parser.start_pos in ignore (Parser.optional p Token.Bar); - parse_type_constructor_declaration ~start_pos p + let constr = parse_type_constructor_declaration ~start_pos p in + {constr with pcd_attributes = doc_comment_attrs @ constr.pcd_attributes} | Some first_constr_decl -> first_constr_decl in first_constr_decl @@ -4948,7 +4979,7 @@ and parse_type_representation ?current_type_name_path ?inline_types_context p = in let kind = match p.Parser.token with - | Bar | Uident _ -> + | Bar | Uident _ | DocComment _ -> Parsetree.Ptype_variant (parse_type_constructor_declarations p) | Lbrace -> Parsetree.Ptype_record @@ -5501,7 +5532,7 @@ and parse_type_equation_and_representation ?current_type_name_path parse_record_or_object_decl ?current_type_name_path ?inline_types_context p | Private -> parse_private_eq_or_repr p - | Bar | DotDot -> + | Bar | DotDot | DocComment _ -> let priv, kind = parse_type_representation p in (None, priv, kind) | _ -> ( diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 4d00515e30..7e2c76a32b 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -340,7 +340,8 @@ let print_list ~get_loc ~nodes ~print ?(force_break = false) t = in Doc.breakable_group ~force_break docs -let print_listi ~get_loc ~nodes ~print ?(force_break = false) t = +let print_listi ~get_loc ~nodes ~print ?(ignore_empty_lines = false) + ?(force_break = false) t = let rec loop i (prev_loc : Location.t) acc nodes = match nodes with | [] -> (prev_loc, Doc.concat (List.rev acc)) @@ -352,8 +353,10 @@ let print_listi ~get_loc ~nodes ~print ?(force_break = false) t = | Some comment -> (Comment.loc comment).loc_start in let sep = - if start_pos.pos_lnum - prev_loc.loc_end.pos_lnum > 1 then - Doc.concat [Doc.hard_line; Doc.hard_line] + if + start_pos.pos_lnum - prev_loc.loc_end.pos_lnum > 1 + && not ignore_empty_lines + then Doc.concat [Doc.hard_line; Doc.hard_line] else Doc.line in let doc = print_comments (print node t i) t loc in @@ -1542,7 +1545,7 @@ and print_constructor_declarations ~state ~private_flag ~print:(fun cd cmt_tbl i -> let doc = print_constructor_declaration2 ~state i cd cmt_tbl in print_comments doc cmt_tbl cd.Parsetree.pcd_loc) - ~force_break cmt_tbl + ~force_break cmt_tbl ~ignore_empty_lines:true in Doc.breakable_group ~force_break (Doc.indent (Doc.concat [Doc.line; private_flag; rows])) @@ -1555,7 +1558,8 @@ and print_constructor_declaration2 ~state i let comment_doc = match comment_attrs with | [] -> Doc.nil - | comment_attrs -> print_doc_comments ~state cmt_tbl comment_attrs + | comment_attrs -> + print_doc_comments ~sep:Doc.hard_line ~state cmt_tbl comment_attrs in let attrs = print_attributes ~state attrs cmt_tbl in let is_dot_dot_dot = cd.pcd_name.txt = "..." in @@ -1579,8 +1583,8 @@ and print_constructor_declaration2 ~state i in Doc.concat [ - bar; comment_doc; + bar; Doc.group (Doc.concat [ diff --git a/tests/syntax_benchmarks/data/Napkinscript.res b/tests/syntax_benchmarks/data/Napkinscript.res index 03093ace2f..2a026803e8 100644 --- a/tests/syntax_benchmarks/data/Napkinscript.res +++ b/tests/syntax_benchmarks/data/Napkinscript.res @@ -1926,15 +1926,12 @@ module Grammar = { | PatternList | PatternOcamlList | PatternRecord - | TypeDef | TypeConstrName | TypeParams | @live TypeParam | PackageConstraint - | TypeRepresentation - | RecordDecl | ConstructorDeclaration | ParameterList diff --git a/tests/tests/src/arith_syntax.res b/tests/tests/src/arith_syntax.res index e4e2980ba7..14be6752f1 100644 --- a/tests/tests/src/arith_syntax.res +++ b/tests/tests/src/arith_syntax.res @@ -1,16 +1,16 @@ type rec expression = - | /** non-negative integer constant */ - Numeral(float) - | /** Addition [e1 + e2] */ - Plus(expression, expression) - | /** Difference [e1 - e2] */ - Minus(expression, expression) - | /** Product [e1 * e2] */ - Times(expression, expression) - | /** Quotient [e1 / e2] */ - Divide(expression, expression) - | /** Opposite value [-e] */ - Negate(expression) + /** non-negative integer constant */ + | Numeral(float) + /** Addition [e1 + e2] */ + | Plus(expression, expression) + /** Difference [e1 - e2] */ + | Minus(expression, expression) + /** Product [e1 * e2] */ + | Times(expression, expression) + /** Quotient [e1 / e2] */ + | Divide(expression, expression) + /** Opposite value [-e] */ + | Negate(expression) | Variable(string) let rec str = e => diff --git a/tests/tests/src/condition_compilation_test.mjs b/tests/tests/src/condition_compilation_test.mjs index 533679e9e7..9ac6ac50a1 100644 --- a/tests/tests/src/condition_compilation_test.mjs +++ b/tests/tests/src/condition_compilation_test.mjs @@ -33,9 +33,9 @@ function eq(loc, x, y) { }; } -eq("File \"condition_compilation_test.res\", line 63, characters 5-12", 3, 3); +eq("File \"condition_compilation_test.res\", line 60, characters 5-12", 3, 3); -eq("File \"condition_compilation_test.res\", line 64, characters 5-12", v.contents, 2); +eq("File \"condition_compilation_test.res\", line 61, characters 5-12", v.contents, 2); Mt.from_pair_suites("Condition_compilation_test", suites.contents); diff --git a/tests/tests/src/condition_compilation_test.res b/tests/tests/src/condition_compilation_test.res index 5bf56bed1b..8c7a8629ac 100644 --- a/tests/tests/src/condition_compilation_test.res +++ b/tests/tests/src/condition_compilation_test.res @@ -15,11 +15,8 @@ type open_flag = | O_DSYNC | O_SYNC | O_RSYNC - | O_SHARE_DELETE - | O_CLOEXEC - | O_KEEPEXEC let vv = 3 diff --git a/tests/tests/src/gpr_1822_test.mjs b/tests/tests/src/gpr_1822_test.mjs index 053f75f08a..c6a595a565 100644 --- a/tests/tests/src/gpr_1822_test.mjs +++ b/tests/tests/src/gpr_1822_test.mjs @@ -34,7 +34,7 @@ let area; area = myShape.TAG === "Circle" ? 100 * 3.14 : 10 * myShape._1 | 0; -eq("File \"gpr_1822_test.res\", line 23, characters 3-10", area, 314); +eq("File \"gpr_1822_test.res\", line 22, characters 3-10", area, 314); Mt.from_pair_suites("Gpr_1822_test", suites.contents); diff --git a/tests/tests/src/gpr_1822_test.res b/tests/tests/src/gpr_1822_test.res index 0d9006280f..a83c2a036c 100644 --- a/tests/tests/src/gpr_1822_test.res +++ b/tests/tests/src/gpr_1822_test.res @@ -11,7 +11,6 @@ let eq = (loc, x, y) => { type shape = | Circle(int) - | Rectangle(int, int) let myShape = Circle(10) diff --git a/tests/tests/src/mutual_non_recursive_type.res b/tests/tests/src/mutual_non_recursive_type.res index 4b3a740bfb..e07c2a84b5 100644 --- a/tests/tests/src/mutual_non_recursive_type.res +++ b/tests/tests/src/mutual_non_recursive_type.res @@ -9,7 +9,7 @@ type t = | Ta(t) /* * u compilation error [nonrec applices to all] */ | Tb(int) and u = - | /** one attribute nonrecursive will affect all */ - H(t) /* refers to old t */ + /** one attribute nonrecursive will affect all */ + | H(t) /* refers to old t */ let v: u = H(OT) diff --git a/tests/tests/src/test_seq.res b/tests/tests/src/test_seq.res index 8927d6119a..fd9bead672 100644 --- a/tests/tests/src/test_seq.res +++ b/tests/tests/src/test_seq.res @@ -29,7 +29,6 @@ type rec spec = | Set_float(ref<float>) /* Set the reference to the float argument */ | Tuple(list<spec>) /* Take several arguments according to the spec list */ - | Symbol(list<string>, string => unit) /* Take one of the symbols as argument and call the function with the symbol. */ diff --git a/tests/tools_tests/src/DocExtractionRes.res b/tests/tools_tests/src/DocExtractionRes.res index e86ab142e3..444cb8e12c 100644 --- a/tests/tools_tests/src/DocExtractionRes.res +++ b/tests/tools_tests/src/DocExtractionRes.res @@ -28,12 +28,12 @@ let \"SomeConstant" = 12 module SomeInnerModule = { /*** Another module level docstring here.*/ type status = - | /** If this is started or not */ - Started(t) - | /** Stopped? */ - Stopped - | /** Now idle.*/ - Idle + /** If this is started or not */ + | Started(t) + /** Stopped? */ + | Stopped + /** Now idle.*/ + | Idle /** These are all the valid inputs.*/ type validInputs = [#something | #"needs-escaping" | #withPayload(int) | #status(status)] @@ -55,8 +55,8 @@ module AnotherModule = { /** Trying how it looks with an inline record in a variant. */ type someVariantWithInlineRecords = - | /** This has inline records...*/ - SomeStuff({ + /** This has inline records...*/ + | SomeStuff({ offline: bool, /** Is the user online? */ online?: bool,