Qualified first type argument after `<` is parsed as `Lident` instead of `Ldot`
Nobody has claimed this yet.
- Dominant language
- OCaml
- Stars
- 10.3k
- Forks
- 438
- PR merge metrics
- No merged PRs in 30d
Description
According to my hours-long talk to a LLM, I have just found a non-reported bug in Reason parser. I am NOT an expert in any of this, but I tried to double check as much as I could before reporting it. It looks reasonable to me.
The following is generated by an LLM and carefully edited by me.
Versions
- Reason 3.18.0
- OCaml 5.2.0
- Melange 7.0.1-52
Minimal reproduction
create files
Create qualified_type.ml:
type t = string
Create with_space.re:
type values = array< Qualified_type.t>;
Create without_space.re:
type values = array<Qualified_type.t>;
compile
NOTE: set switch with eval "$(opam env --root="$HOME/.opam" --switch=hippercampus_melange --set-switch)"
Compile the defining module and parse both Reason files:
melc -c qualified_type.ml -o _output/qualified_type.cmj
refmt --parse re --print binary with_space.re > _output/with_space.pp.ml
refmt --parse re --print binary without_space.re > _output/without_space.pp.ml
Type-check both parser outputs:
melc -I _output -i _output/with_space.pp.ml
melc -I _output -i _output/without_space.pp.ml
check the types created
From the studies directory, compile the AST inspector:
ocamlfind ocamlc -package compiler-libs.common -c inspect_longident.ml -o _output/inspect_longident.cmo
ocamlfind ocamlc -package compiler-libs.common -linkpkg _output/inspect_longident.cmo -o _output/inspect_longident.exe
compare the types:
_output/inspect_longident.exe \
_output/with_space.pp.ml \
_output/with_space.inspected.pp.ml
Lident("array")
Ldot(Lident("Qualified_type"), "t")
_output/inspect_longident.exe \
_output/without_space.pp.ml \
_output/without_space.inspected.pp.ml
Lident("array")
Lident("Qualified_type.t")
FAIL: Ldot(Lident("Qualified_type"), "t") vs Lident("Qualified_type.t")
Actual behavior
The spaced form succeeds. The unspaced form fails:
Error: Unbound type constructor Qualified_type.t
The spaced form represents the qualified type correctly:
Ldot(Lident("Qualified_type"), "t")
The unspaced form represents the same qualified type as one identifier containing a dot:
Lident("Qualified_type.t")
Expected behavior
Both forms should produce:
Ldot(Lident("Qualified_type"), "t")
Alternatively, if the unspaced form is not supported, the parser should reject
it as invalid syntax instead of creating a malformed qualified identifier.
Diagnosis
The lexer consumes <Qualified_type.t as one LESSIDENT token. The
first_less_than_type_ident grammar rule then constructs it unconditionally
as:
Ppxlib.Longident.Lident $1
This is valid for an unqualified name but incorrect when $1 contains a
module-qualified type.
The same rule is present in the current Reason master branch:
Workarounds
The spaced form works:
type values = array< Qualified_type.t>;
The canonical parenthesized Reason form also works:
type values = array(Qualified_type.t);
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/reason-parser/reason_declarative_lexer.mll and src/reason-parser/reason_parser.mly, focusing on the LESSIDENT token and first_less_than_type_ident rule. Reproduce the spaced and unspaced examples with refmt and inspect their outputs. Done means the unspaced qualified type is represented as Ldot like the spaced form, or is rejected as invalid syntax.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ocaml
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100