Skip to content

Parse attributes after = private and a doc comment before a manifest - #9115

Draft
cknitt wants to merge 1 commit into
masterfrom
fix-9106-private-attr-doc-comment
Draft

cknitt wants to merge 1 commit into
masterfrom
fix-9106-private-attr-doc-comment

Conversation

@cknitt

@cknitt cknitt commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

type t = private @attr {x: int} and type z = /** d */ int were both rejected, although type t = @attr {x: int}, type t = private {x: int} and type z = @attr int parse.

After = private, an attribute counts as the start of a type expression, so @attr {x: int} was parsed as a manifest and the record failed. After =, a doc comment was always sent to the type representation parser, which then expected a constructor, so /** d */ int, {"a": int}, (int, int), [#A] and M.t failed.

The lookahead that already decided between representation and manifest for an attribute after = is now a shared helper, used for both attributes and doc comments, after = and after = private: past attributes and doc comments, a record, .., | or a constructor makes a representation, anything else a manifest. Attributes and doc comments before a private record or .. go on the type declaration, as for the non-private forms (#8919).

Tests: new cases in the parser fixture typedefinition/typeRepresentation.res (attributes and doc comments after private before a record, .., constructors, a type and an object; doc comments after = before int, an object, a tuple, a polymorphic variant, M.t and M.t = A | B) and in the printer fixture typeDef/attributes.res, which also checks that the newly accepted forms round-trip.

Not changed: type t = private {x: {y: int}} (a nested record after private) is still rejected, with or without an attribute; that is a separate limitation of the private record path. A spread keeps its old meaning: after a doc comment {...base, y: int} is a record declaration, after an attribute an object type, so @attr {...base, y: int} and /** d */ {...base, "y": int} are still rejected, as on master; tracked in #9122.

A differential run against master over 672 declarations (no prefix, one or two doc comments, an attribute, and both orders of doc comment and attribute, times 28 bodies such as constructors, variant and record spreads, records, objects, .., int, M.t = A, 'a and int => int; with and without private; in .res and .resi) gives the same parse tree for every declaration master accepts. The only changes are forms master rejected: a doc comment before a type expression or object, and an attribute after private before constructors, a record or ...

Fixes #9106

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kbf3gGkPma9LAZimuQjnrP

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Changelog

🐛 Bug fix

  • Accept attributes and doc comments after = private, and a doc comment after = in front of a type expression such as int.

Semantic Diff

1 commit · Full page

✏️ Parse attributes after = private and a doc comment before a manifest

bea1cbc · cknitt + Claude · Oct 28, 2026, 15:34 UTC · edited

Why

private @attr {x: int} took the type expression path and rejected the record, and a doc comment right after = always started a constructor list, so /** d */ int failed. Both now use the lookahead that attributes after = already had: past attributes and doc comments, a record, .., | or constructor is a representation, anything else a manifest.

AST modified (doc comment before a manifest)

type z = /** d */ int

diagnostic · Before

Did you mean `Int` instead of `int`?

parse · After

type_declaration "z"
  ptype_kind =
    Ptype_abstract
  ptype_private = Public
  ptype_manifest =
    Some
      core_type
        attribute  "res.doc"
        …
        Ptyp_constr "int"

Format modified (attribute after private)

type t = private @attr {x: int}
type u = private /** d */ int

diagnostic · Before

An inline record type declaration is only allowed in a variant
constructor's declaration or nested inside of a record type declaration

rescript · After

@attr type t = private {x: int}
type u = private /** d */ int

Fixes #9106

Review

  • Does a leading doc comment change any form that parsed before? No. After a doc comment the lookahead keeps the representation for every start the constructor and record parsers accept (|, a constructor, ...base, {..., a record, ..); only starts they rejected, such as int, M.t, 'a, (a, b), [#A], {.} or {"a": int}, become a manifest. A differential run over 672 declarations (prefixes x bodies, with and without private, .res and .resi) gives the same tree as before for every one that parsed.

@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 81.46%. Comparing base (85f31fb) to head (bea1cbc).

Files with missing lines Patch % Lines
compiler/syntax/src/res_core.ml 96.29% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #9115   +/-   ##
=======================================
  Coverage   81.46%   81.46%           
=======================================
  Files         460      460           
  Lines       64675    64684    +9     
=======================================
+ Hits        52689    52698    +9     
  Misses      11986    11986           
Files with missing lines Coverage Δ
compiler/syntax/src/res_core.ml 92.02% <96.29%> (+0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@9115

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@9115

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@9115

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@9115

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@9115

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@9115

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@9115

commit: bea1cbc

@github-actions

Copy link
Copy Markdown

@cknitt

cknitt commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a8d70f94b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/syntax/src/res_core.ml Outdated
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T16:52:18.922384Z bea1cbc Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cknitt
cknitt force-pushed the fix-9106-private-attr-doc-comment branch from 5a8d70f to 6883c20 Compare October 28, 2026 16:45
@cknitt

cknitt commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6883c20c44

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/syntax/src/res_core.ml
Why:
    `private @attr {x: int}` took the type expression path and rejected
    the record, and a doc comment right after `=` always started a
    constructor list, so `/** d */ int` failed. Both now use the
    lookahead that attributes after `=` already had: past attributes and
    doc comments, a record, `..`, `|` or constructor is a representation,
    anything else a manifest.

AST modified (doc comment before a manifest):
Example:
    type z = /** d */ int
Before (diagnostic):
    Did you mean `Int` instead of `int`?
After (parse):
    type_declaration "z"
      ptype_kind =
        Ptype_abstract
      ptype_private = Public
      ptype_manifest =
        Some
          core_type
            attribute  "res.doc"
            …
            Ptyp_constr "int"

Format modified (attribute after `private`):
Example:
    type t = private @attr {x: int}
    type u = private /** d */ int
Before (diagnostic):
    An inline record type declaration is only allowed in a variant
    constructor's declaration or nested inside of a record type declaration
After (rescript):
    @attr type t = private {x: int}
    type u = private /** d */ int

Review: Does a leading doc comment change any form that parsed before? No.
After a doc comment the lookahead keeps the representation for every
start the constructor and record parsers accept (`|`, a constructor,
`...base`, `{...`, a record, `..`); only starts they rejected, such as
`int`, `M.t`, `'a`, `(a, b)`, `[#A]`, `{.}` or `{"a": int}`, become a
manifest. A differential run over 672 declarations (prefixes x bodies,
with and without `private`, .res and .resi) gives the same tree as before
for every one that parsed.

Fixes #9106

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Kbf3gGkPma9LAZimuQjnrP
Signed-off-by: Christoph Knittel <[email protected]>
@cknitt
cknitt force-pushed the fix-9106-private-attr-doc-comment branch from 6883c20 to bea1cbc Compare October 28, 2026 16:50
@cknitt

cknitt commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: bea1cbcda3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parser rejects private @attr {…} and a doc comment before a non-record type

1 participant