Skip to content

Support doc comment before variant #7535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"mcpServers": {
"ocamllsp": {
"type": "stdio",
"command": "mcp-language-server",
"args": [
"--workspace",
"/Users/shulhi/Dev/rescript/rescript-compiler",
"--lsp",
"ocamllsp"
],
"env": {}
}
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
139 changes: 139 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions Example.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type x =
/** first group */
| A
| B
| C

/** second group */
| D
| E
| F
45 changes: 38 additions & 7 deletions compiler/syntax/src/res_core.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
| _ -> (
Expand Down
16 changes: 10 additions & 6 deletions compiler/syntax/src/res_printer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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]))
Expand All @@ -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
Expand All @@ -1579,8 +1583,8 @@ and print_constructor_declaration2 ~state i
in
Doc.concat
[
bar;
comment_doc;
bar;
Doc.group
(Doc.concat
[
Expand Down
3 changes: 0 additions & 3 deletions tests/syntax_benchmarks/data/Napkinscript.res
Original file line number Diff line number Diff line change
Expand Up @@ -1926,15 +1926,12 @@ module Grammar = {
| PatternList
| PatternOcamlList
| PatternRecord

| TypeDef
| TypeConstrName
| TypeParams
| @live TypeParam
| PackageConstraint

| TypeRepresentation

| RecordDecl
| ConstructorDeclaration
| ParameterList
Expand Down
24 changes: 12 additions & 12 deletions tests/tests/src/arith_syntax.res
Original file line number Diff line number Diff line change
@@ -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 =>
Expand Down
4 changes: 2 additions & 2 deletions tests/tests/src/condition_compilation_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 0 additions & 3 deletions tests/tests/src/condition_compilation_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@ type open_flag =
| O_DSYNC
| O_SYNC
| O_RSYNC

| O_SHARE_DELETE

| O_CLOEXEC

| O_KEEPEXEC
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this PR, no newlines in between variant constructors. This makes the implementation simpler since I don't need to hack around node's location.

Personally, I prefer the formatting this way too. What do you think? @cknitt @zth @nojaf

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for me!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! 🎉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I meant the new formatting of doc comments before variants is great.

Regarding the newlines, it would be nice if this was still possible:

type x =
  // first group
  | A
  | B
  | C

  // second group
  | D
  | E
  | F

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just put an empty comment line where you want the break?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shulhi, this is better! I wasn't a fan of adjusting the ranges in the parser to satisfy the format. It felt wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will reformat it to

type x =
  // first group
  | A
  | B
  | C
  // second group
  | D
  | E
  | F

I can still investigate this if we want to preserve the previous behavior. I actually like it without the newline.

The challenge with preserving the previous behavior has to do with how we treat comments and doc comments (comment table and attribute). If we compare the location for the comment for the above example, first group is attached to line 3, while it would be at line 2 for doc comment. We would have to adjust the ranges to make it print the same way for both - which felt like a hack.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that sounds quite hacky indeed.
I guess I can live with no newlines in that case. 🙂


let vv = 3
Expand Down
2 changes: 1 addition & 1 deletion tests/tests/src/gpr_1822_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 0 additions & 1 deletion tests/tests/src/gpr_1822_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ let eq = (loc, x, y) => {

type shape =
| Circle(int)

| Rectangle(int, int)

let myShape = Circle(10)
Expand Down
Loading