reasonml / reasonml/reason

Qualified first type argument after `<` is parsed as `Lident` instead of `Ldot`

Open
#2,943 0 comments 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.