Skip to content

Fix type region ending early after ranges and missing -nominal - #20

Merged
robertoaloi merged 2 commits into
mainfrom
fix/type-region-edge-cases
Sep 30, 2026
Merged

robertoaloi merged 2 commits into
mainfrom
fix/type-region-edge-cases

Conversation

@robertoaloi

Copy link
Copy Markdown
Member

Follow-up to #19. In two cases, a fun type in a type directive falls outside the meta.type.erlang region, so the function type injection doesn't apply. The fun is then highlighted as a fun expression, and everything after it is highlighted as the inside of a fun, down to the end of the file:

-type t() :: 1..10 | fun(() -> ok).
-spec f(X, Y) -> ok when X :: 0..255, Y :: fun(() -> ok).
-nominal n() :: fun(() -> ok).

Do not end type directives at the dot of a range

The type-directive end pattern (\)?)\s*+(\.) matched the first . of .., which closed the region before the fun(. The directive now only ends at a dot followed by whitespace, a % comment or the end of the line, the same way Erlang ends a form. Ranges inside (), [] or {} were never affected.

Treat -nominal as a type directive

Nominal types (OTP 28) went to the generic #directive rule and never got meta.type.erlang. nominal is now in the list of directives that type-directive matches.

Tests

Each commit adds cases to tests/fun_expression.erl, and each case failed before its fix. The cases check that the fun is scoped as a function type, that the directive ends at the real dot, and that the next function head is still highlighted correctly. No existing snapshot changed.

The type-directive end pattern `(\)?)\s*+(\.)` matched the first dot of
a range such as `1..10`, closing the meta.type.erlang region early. A
fun type after the range was then no longer covered by the function
type injection, fell through to the fun expression rules and scoped
the rest of the file as the body of a fun:

    -type t() :: 1..10 | fun(() -> ok).

Only end the directive at a dot followed by whitespace, a comment or
the end of the line, which is how Erlang itself terminates a form.
Nominal types (OTP 28) were handled by the generic directive rule, so
they never got the meta.type.erlang scope. A fun type in one fell
through to the fun expression rules and scoped the rest of the file as
the body of a fun:

    -nominal n() :: fun(() -> ok).

Add nominal to the directives matched by type-directive.
@robertoaloi

Copy link
Copy Markdown
Member Author

cc @pgourlain

@robertoaloi
robertoaloi merged commit 6542e3c into main Sep 30, 2026
2 checks passed
meta-codesync Bot pushed a commit to WhatsApp/erlang-language-platform that referenced this pull request Sep 30, 2026
Summary:
Sync both copies of the Erlang TextMate grammar (the ELP extension and the internal `nuclide.erlang` extension) with https://github.com/erlang-ls/grammar at `49f7a9a`. Both copies now match upstream byte for byte.

Changes since the last sync:

- erlang-ls/grammar#19: named fun expressions (`fun Name(...) -> ... end`) are highlighted correctly, and no longer scope the rest of the file as an implicit fun. Zero-arity fun expressions such as `fun() -> ok end` are keywords instead of function types. The function type rule now only applies inside types (`-type`, `-opaque`, `-spec`, `-callback`, typed record fields), via a new `meta.type.erlang` scope. Fixes pgourlain/vscode_erlang#316 and #317.
- erlang-ls/grammar#20: a fun type after a top-level range (`-type t() :: 1..10 | fun(() -> ok).`) or in a `-nominal` type no longer scopes the rest of the file as a fun expression.
- erlang-ls/grammar#21: upstreams the fix for Markdown leaking out of `-doc`/`-moduledoc` attributes (D98300495), which only lived in these copies. No behaviour change here.
- erlang-ls/grammar#22: follow-ups from the review of this diff. Named funs called `_` (`fun _(A) -> A end`) are highlighted as named funs. `README.md` typos fixed (`ignode`, `./test/snap`) and the snapshot update command loads the Markdown test grammar. Upstream also adds tests showing that a comma nested in a record field type does not end the `meta.type.erlang` region, and that a default value stays an expression.

`README.md` is synced too (test instructions and references); `LICENSE` is unchanged.

Reviewed By: jcpetruzza

Differential Revision: D122532634

fbshipit-source-id: cabfee7809fcd0e0977d700bc71b8b50db8cd02f
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.

2 participants