Semantic Diff

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

1 commit

85f31fbb15ef96bd168d6e99055ad4808810d6e0..pr-9115

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

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

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.

modifiedAST 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"

modifiedFormat 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 variantconstructor'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.