Skip to content

Commit ece0702

Browse files
authored
Support doc comment before variant (#7535)
* WIP * Format doc comment before bar * Adjust loc * Update test files * Lookahead if we are in variant * Cleanup * Remove loc adjustment hack * Update test files * Update CHANGELOG * Resolve review feedback * Update tests
1 parent 6ddfd80 commit ece0702

15 files changed

+238
-46
lines changed

.mcp.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"mcpServers": {
3+
"ocamllsp": {
4+
"type": "stdio",
5+
"command": "mcp-language-server",
6+
"args": [
7+
"--workspace",
8+
"/Users/shulhi/Dev/rescript/rescript-compiler",
9+
"--lsp",
10+
"ocamllsp"
11+
],
12+
"env": {}
13+
}
14+
}
15+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- Don't produce duplicate type definitions for recursive types on hover. https://github.com/rescript-lang/rescript/pull/7524
3939
- 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
4040
- Fix partial application with user-defined function types. https://github.com/rescript-lang/rescript/pull/7548
41+
- Fix doc comment before variant throwing syntax error. https://github.com/rescript-lang/rescript/pull/7535
4142

4243
#### :nail_care: Polish
4344

CLAUDE.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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+
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.
8+
9+
## Build Commands
10+
11+
### Basic Development
12+
```bash
13+
# Build compiler and copy executables
14+
make
15+
16+
# Build in watch mode
17+
make watch
18+
19+
# Build everything including standard library
20+
make lib
21+
22+
# Build artifacts and update artifact list
23+
make artifacts
24+
```
25+
26+
### Testing
27+
```bash
28+
# Run all tests
29+
make test
30+
31+
# Run specific test types
32+
make test-syntax # Syntax parser tests
33+
make test-syntax-roundtrip # Roundtrip syntax tests
34+
make test-gentype # GenType tests
35+
make test-analysis # Analysis tests
36+
make test-tools # Tools tests
37+
make test-rewatch # Rewatch tests
38+
39+
# Run single file test
40+
./cli/bsc.js myTestFile.res
41+
42+
# View parse/typed trees for debugging
43+
./cli/bsc.js -dparsetree myTestFile.res
44+
./cli/bsc.js -dtypedtree myTestFile.res
45+
```
46+
47+
### Code Quality
48+
```bash
49+
# Format code
50+
make format
51+
52+
# Check formatting
53+
make checkformat
54+
55+
# Lint with Biome
56+
npm run check
57+
npm run check:all
58+
59+
# TypeScript type checking
60+
npm run typecheck
61+
```
62+
63+
### Clean Operations
64+
```bash
65+
make clean # Clean OCaml build artifacts
66+
make clean-all # Clean everything including Rust/gentype
67+
```
68+
69+
## Compiler Architecture
70+
71+
The ReScript compiler follows this high-level pipeline:
72+
73+
```
74+
ReScript Source (.res)
75+
↓ (ReScript Parser - compiler/syntax/)
76+
Surface Syntax Tree
77+
↓ (Frontend transformations - compiler/frontend/)
78+
Surface Syntax Tree
79+
↓ (OCaml Type Checker - compiler/ml/)
80+
Typedtree
81+
↓ (Lambda compilation - compiler/core/lam_*)
82+
Lambda IR
83+
↓ (JS compilation - compiler/core/js_*)
84+
JS IR
85+
↓ (JS output - compiler/core/js_dump*)
86+
JavaScript Code
87+
```
88+
89+
### Key Directories
90+
91+
- **`compiler/syntax/`** - ReScript syntax parser (MIT licensed, separate from main LGPL)
92+
- **`compiler/frontend/`** - AST transformations, external FFI processing, built-in attributes
93+
- **`compiler/ml/`** - OCaml compiler infrastructure (type checker, typedtree, etc.)
94+
- **`compiler/core/`** - Core compilation:
95+
- `lam_*` files: Lambda IR compilation and optimization passes
96+
- `js_*` files: JavaScript IR and code generation
97+
- **`compiler/ext/`** - Extended utilities and data structures
98+
- **`compiler/bsb/`** - Build system implementation
99+
- **`compiler/gentype/`** - TypeScript generation
100+
- **`runtime/`** - ReScript standard library (written in ReScript)
101+
- **`lib/`** - Compiled JavaScript output of standard library
102+
- **`analysis/`** - Language server and tooling support
103+
104+
### Build System Components
105+
106+
- **`compiler/bsb_exe/`** - Main ReScript build tool entry point
107+
- **`compiler/bsc/`** - Compiler binary entry point
108+
- **`rewatch/`** - File watcher (written in Rust)
109+
- **`ninja/`** - Vendored Ninja build system
110+
111+
## Development Setup Notes
112+
113+
- Uses OCaml 5.3.0+ with opam for compiler development
114+
- Uses dune as build system with specific profiles (dev, release, browser)
115+
- Node.js 20+ required for JavaScript tooling
116+
- Rust toolchain needed for rewatch file watcher
117+
- Python ≤3.11 required for building ninja
118+
119+
## Coding Conventions
120+
121+
- **OCaml code**: snake_case (e.g., `to_string`)
122+
- **ReScript code**: camelCase (e.g., `toString`)
123+
- Use DCO sign-off for all commits: `Signed-Off-By: Your Name <email>`
124+
125+
## Testing Strategy
126+
127+
- **Mocha tests** (`tests/tests/`) - Runtime library unit tests
128+
- **Build system tests** (`tests/build_tests/`) - Integration tests
129+
- **OUnit tests** (`tests/ounit_tests/`) - Compiler unit tests
130+
- **Expectation tests** - Plain `.res` files that check compilation output
131+
- Always include appropriate tests with new features/changes
132+
133+
## Performance Notes
134+
135+
The compiler is designed for fast feedback loops and scales to large codebases. When making changes:
136+
- Avoid introducing meaningless symbols
137+
- Maintain readable JavaScript output
138+
- Consider compilation speed impact
139+
- Use appropriate optimization passes in the Lambda and JS IRs

Example.res

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type x =
2+
/** first group */
3+
| A
4+
| B
5+
| C
6+
7+
/** second group */
8+
| D
9+
| E
10+
| F

compiler/syntax/src/res_core.ml

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4886,12 +4886,35 @@ and parse_constr_decl_args p =
48864886
* | constr-name const-args
48874887
* | attrs constr-name const-args *)
48884888
and parse_type_constructor_declaration_with_bar p =
4889-
match p.Parser.token with
4890-
| Bar ->
4889+
let is_constructor_with_bar p =
4890+
Parser.lookahead p (fun state ->
4891+
match state.Parser.token with
4892+
| DocComment _ -> (
4893+
Parser.next state;
4894+
match state.token with
4895+
| Bar -> true
4896+
| _ -> false)
4897+
| Bar -> true
4898+
| _ -> false)
4899+
in
4900+
if is_constructor_with_bar p then (
4901+
let doc_comment_attrs =
4902+
match p.Parser.token with
4903+
| DocComment (loc, s) ->
4904+
Parser.next p;
4905+
[doc_comment_to_attribute loc s]
4906+
| _ -> []
4907+
in
48914908
let start_pos = p.Parser.start_pos in
48924909
Parser.next p;
4893-
Some (parse_type_constructor_declaration ~start_pos p)
4894-
| _ -> None
4910+
let constr = parse_type_constructor_declaration ~start_pos p in
4911+
Some
4912+
{
4913+
constr with
4914+
Parsetree.pcd_attributes =
4915+
doc_comment_attrs @ constr.Parsetree.pcd_attributes;
4916+
})
4917+
else None
48954918

48964919
and parse_type_constructor_declaration ~start_pos p =
48974920
Parser.leave_breadcrumb p Grammar.ConstructorDeclaration;
@@ -4920,9 +4943,17 @@ and parse_type_constructor_declarations ?first p =
49204943
let first_constr_decl =
49214944
match first with
49224945
| None ->
4946+
let doc_comment_attrs =
4947+
match p.Parser.token with
4948+
| DocComment (loc, s) ->
4949+
Parser.next p;
4950+
[doc_comment_to_attribute loc s]
4951+
| _ -> []
4952+
in
49234953
let start_pos = p.Parser.start_pos in
49244954
ignore (Parser.optional p Token.Bar);
4925-
parse_type_constructor_declaration ~start_pos p
4955+
let constr = parse_type_constructor_declaration ~start_pos p in
4956+
{constr with pcd_attributes = doc_comment_attrs @ constr.pcd_attributes}
49264957
| Some first_constr_decl -> first_constr_decl
49274958
in
49284959
first_constr_decl
@@ -4948,7 +4979,7 @@ and parse_type_representation ?current_type_name_path ?inline_types_context p =
49484979
in
49494980
let kind =
49504981
match p.Parser.token with
4951-
| Bar | Uident _ ->
4982+
| Bar | Uident _ | DocComment _ ->
49524983
Parsetree.Ptype_variant (parse_type_constructor_declarations p)
49534984
| Lbrace ->
49544985
Parsetree.Ptype_record
@@ -5501,7 +5532,7 @@ and parse_type_equation_and_representation ?current_type_name_path
55015532
parse_record_or_object_decl ?current_type_name_path ?inline_types_context
55025533
p
55035534
| Private -> parse_private_eq_or_repr p
5504-
| Bar | DotDot ->
5535+
| Bar | DotDot | DocComment _ ->
55055536
let priv, kind = parse_type_representation p in
55065537
(None, priv, kind)
55075538
| _ -> (

compiler/syntax/src/res_printer.ml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,8 @@ let print_list ~get_loc ~nodes ~print ?(force_break = false) t =
340340
in
341341
Doc.breakable_group ~force_break docs
342342

343-
let print_listi ~get_loc ~nodes ~print ?(force_break = false) t =
343+
let print_listi ~get_loc ~nodes ~print ?(ignore_empty_lines = false)
344+
?(force_break = false) t =
344345
let rec loop i (prev_loc : Location.t) acc nodes =
345346
match nodes with
346347
| [] -> (prev_loc, Doc.concat (List.rev acc))
@@ -352,8 +353,10 @@ let print_listi ~get_loc ~nodes ~print ?(force_break = false) t =
352353
| Some comment -> (Comment.loc comment).loc_start
353354
in
354355
let sep =
355-
if start_pos.pos_lnum - prev_loc.loc_end.pos_lnum > 1 then
356-
Doc.concat [Doc.hard_line; Doc.hard_line]
356+
if
357+
start_pos.pos_lnum - prev_loc.loc_end.pos_lnum > 1
358+
&& not ignore_empty_lines
359+
then Doc.concat [Doc.hard_line; Doc.hard_line]
357360
else Doc.line
358361
in
359362
let doc = print_comments (print node t i) t loc in
@@ -1542,7 +1545,7 @@ and print_constructor_declarations ~state ~private_flag
15421545
~print:(fun cd cmt_tbl i ->
15431546
let doc = print_constructor_declaration2 ~state i cd cmt_tbl in
15441547
print_comments doc cmt_tbl cd.Parsetree.pcd_loc)
1545-
~force_break cmt_tbl
1548+
~force_break cmt_tbl ~ignore_empty_lines:true
15461549
in
15471550
Doc.breakable_group ~force_break
15481551
(Doc.indent (Doc.concat [Doc.line; private_flag; rows]))
@@ -1555,7 +1558,8 @@ and print_constructor_declaration2 ~state i
15551558
let comment_doc =
15561559
match comment_attrs with
15571560
| [] -> Doc.nil
1558-
| comment_attrs -> print_doc_comments ~state cmt_tbl comment_attrs
1561+
| comment_attrs ->
1562+
print_doc_comments ~sep:Doc.hard_line ~state cmt_tbl comment_attrs
15591563
in
15601564
let attrs = print_attributes ~state attrs cmt_tbl in
15611565
let is_dot_dot_dot = cd.pcd_name.txt = "..." in
@@ -1579,8 +1583,8 @@ and print_constructor_declaration2 ~state i
15791583
in
15801584
Doc.concat
15811585
[
1582-
bar;
15831586
comment_doc;
1587+
bar;
15841588
Doc.group
15851589
(Doc.concat
15861590
[

tests/syntax_benchmarks/data/Napkinscript.res

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,15 +1926,12 @@ module Grammar = {
19261926
| PatternList
19271927
| PatternOcamlList
19281928
| PatternRecord
1929-
19301929
| TypeDef
19311930
| TypeConstrName
19321931
| TypeParams
19331932
| @live TypeParam
19341933
| PackageConstraint
1935-
19361934
| TypeRepresentation
1937-
19381935
| RecordDecl
19391936
| ConstructorDeclaration
19401937
| ParameterList

tests/tests/src/arith_syntax.res

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
type rec expression =
2-
| /** non-negative integer constant */
3-
Numeral(float)
4-
| /** Addition [e1 + e2] */
5-
Plus(expression, expression)
6-
| /** Difference [e1 - e2] */
7-
Minus(expression, expression)
8-
| /** Product [e1 * e2] */
9-
Times(expression, expression)
10-
| /** Quotient [e1 / e2] */
11-
Divide(expression, expression)
12-
| /** Opposite value [-e] */
13-
Negate(expression)
2+
/** non-negative integer constant */
3+
| Numeral(float)
4+
/** Addition [e1 + e2] */
5+
| Plus(expression, expression)
6+
/** Difference [e1 - e2] */
7+
| Minus(expression, expression)
8+
/** Product [e1 * e2] */
9+
| Times(expression, expression)
10+
/** Quotient [e1 / e2] */
11+
| Divide(expression, expression)
12+
/** Opposite value [-e] */
13+
| Negate(expression)
1414
| Variable(string)
1515

1616
let rec str = e =>

tests/tests/src/condition_compilation_test.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ function eq(loc, x, y) {
3333
};
3434
}
3535

36-
eq("File \"condition_compilation_test.res\", line 63, characters 5-12", 3, 3);
36+
eq("File \"condition_compilation_test.res\", line 60, characters 5-12", 3, 3);
3737

38-
eq("File \"condition_compilation_test.res\", line 64, characters 5-12", v.contents, 2);
38+
eq("File \"condition_compilation_test.res\", line 61, characters 5-12", v.contents, 2);
3939

4040
Mt.from_pair_suites("Condition_compilation_test", suites.contents);
4141

tests/tests/src/condition_compilation_test.res

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@ type open_flag =
1515
| O_DSYNC
1616
| O_SYNC
1717
| O_RSYNC
18-
1918
| O_SHARE_DELETE
20-
2119
| O_CLOEXEC
22-
2320
| O_KEEPEXEC
2421

2522
let vv = 3

tests/tests/src/gpr_1822_test.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let area;
3434

3535
area = myShape.TAG === "Circle" ? 100 * 3.14 : 10 * myShape._1 | 0;
3636

37-
eq("File \"gpr_1822_test.res\", line 23, characters 3-10", area, 314);
37+
eq("File \"gpr_1822_test.res\", line 22, characters 3-10", area, 314);
3838

3939
Mt.from_pair_suites("Gpr_1822_test", suites.contents);
4040

tests/tests/src/gpr_1822_test.res

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ let eq = (loc, x, y) => {
1111

1212
type shape =
1313
| Circle(int)
14-
1514
| Rectangle(int, int)
1615

1716
let myShape = Circle(10)

0 commit comments

Comments
 (0)