Skip to content

[JuliaSyntax] Remove separate syntax heads for each operator#62000

Merged
Keno merged 1 commit into
JuliaLang:masterfrom
KenoAIStaging:kf/syntax-rmopkinds
Jun 10, 2026
Merged

[JuliaSyntax] Remove separate syntax heads for each operator#62000
Keno merged 1 commit into
JuliaLang:masterfrom
KenoAIStaging:kf/syntax-rmopkinds

Conversation

@Keno

@Keno Keno commented Jun 4, 2026

Copy link
Copy Markdown
Member

[Rebase of https://github.com/JuliaLang/JuliaSyntax.jl/pull/575 plus Claude's updates to JuliaLowering]

This replaces all the specialized operator heads by a single K"Operator" head that encodes the precedence level in its flags (except for operators that are also used for non-operator purposes). The operators are already K"Identifier" in the final parse tree. There is very little reason to spend all of the extra effort separating them into separate heads only to undo this later. Moreover, I think it's actively misleading, because it makes people think that they can query things about an operator by looking at the head, which doesn't work for suffixed operators.

Additionally, this removes the op= token, replacing it by two tokens, one K"Operator" with a special precendence level and one =. This then removes the last use of bump_split (since this PR is on top of JuliaLang/JuliaSyntax.jl#573).

As a free bonus this prepares us for having compound assignment syntax for suffixed operators, which was infeasible in the flips parser. That syntax change is not part of this PR but would be trivial (this PR makes it an explicit error).

Because most operators no longer have a dedicated kind, the in-tree consumers of the syntax tree are updated to match:

  • _green_to_est now lowers the one-argument K"op="/K".op=" forms (the quoted compound-assignment operators such as :(+=)) to the corresponding identifier name.
  • JuliaLowering's is_dotted_operator no longer looks up an operator by Kind name (which no longer exists for most operators); it uses the operator-name test Base.isoperator, as already done for op= in est_to_dst.

Fixes JuliaLang/JuliaSyntax.jl#334

(cherry picked from commit 9c41dfa55e460ff50906644f21050d1352f01f20)

@Keno
Keno force-pushed the kf/syntax-rmopkinds branch 4 times, most recently from e75229b to 2f7fab2 Compare June 4, 2026 19:51
This replaces all the specialized operator heads by a single K"Operator"
head that encodes the precedence level in its flags (except for operators
that are also used for non-operator purposes). The operators are
already K"Identifier" in the final parse tree. There is very little
reason to spend all of the extra effort separating them into separate
heads only to undo this later. Moreover, I think it's actively misleading,
because it makes people think that they can query things about an operator
by looking at the head, which doesn't work for suffixed operators.

Additionally, this removes the `op=` token, replacing it by two tokens,
one K"Operator" with a special precedence level and one `=`. An operator
only forms a compound assignment when followed by the single token `=`;
when followed by `==`, `===` or `=>` it is emitted as a plain operator
token. This then removes the last use of `bump_split` (since this PR is
on top of JuliaLang/JuliaSyntax.jl#573).

As a free bonus this prepares us for having compound assignment syntax
for suffixed operators, which was infeasible in the flips parser. That
syntax change is not part of this PR but would be trivial (this PR
makes it an explicit error).

Because most operators no longer have a dedicated kind, the in-tree
consumers of the syntax tree are updated to match:
* `_green_to_est` now lowers the one-argument `K"op="`/`K".op="` forms
  (the quoted compound-assignment operators such as `:(+=)`) to the
  corresponding identifier name.
* JuliaLowering's `is_dotted_operator` no longer looks up an operator by
  `Kind` name (which no longer exists for most operators); it uses the
  operator-name test `Base.isoperator`, as already done for `op=` in
  `est_to_dst`.
* JuliaSyntaxHighlighting is updated similarly (detect `===` by text, drop the
  removed `is_syntactic_assignment`, and recognize the now-kindless operators)
  and its stdlib version pin is bumped accordingly.

Fixes JuliaLang/JuliaSyntax.jl#334

(cherry picked from commit 9c41dfa55e460ff50906644f21050d1352f01f20)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Keno
Keno force-pushed the kf/syntax-rmopkinds branch from 2f7fab2 to aadd88f Compare June 9, 2026 22:44
@Keno
Keno merged commit 5a2b7c5 into JuliaLang:master Jun 10, 2026
6 of 9 checks passed
Keno added a commit to KenoAIStaging/julia that referenced this pull request Jun 10, 2026
`a .≔ b` (likewise `.⩴` and `.≕`) crashed the parser with an internal
"Unexpected dotted operator" error since the dots were split into
separate tokens (JuliaLang#61992), because `dotted()` only handled the heads
`.= .&& .|| .op=`. Add dedicated `K".≔"`/`K".⩴"`/`K".≕"` heads -
matching `K".="` - so these parse to `(.≔ a b)` and convert to
`Expr(:.≔, a, b)` as in the reference parser. Dotted `:=` is not valid
syntax (the reference parser rejects it too), so emit a proper parse
error for it instead of crashing.

Also a few smaller items from review of JuliaLang#62000:
* The new heads also fix `Expr(:.≔, a, b)` conversion into a
  `SyntaxTree`, which previously went through JuliaLowering's compound
  assignment `unknown_head` path. That path now additionally requires a
  trailing `=` in the head name, so operator-named heads such as a
  hand-written `Expr(:⊕, a, b)` produce a lowering error rather than
  being mangled into an `op=` form.
* Add `K"Operator"` to `_nonunique_kind_names`, since the kind name
  doesn't determine the token text.
* Remove duplicated `·` (U+00B7) and `·` (U+0387) entries from the
  `PREC_TIMES` operator table (they are listed - with comments - at the
  end of the list).
* Fix an asymmetry in `Expr` conversion of one-argument `K".op="` vs
  `K"op="` heads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keno added a commit to KenoAIStaging/julia that referenced this pull request Jun 10, 2026
`a .≔ b` (likewise `.⩴` and `.≕`) crashed the parser with an internal
"Unexpected dotted operator" error since the dots were split into
separate tokens (JuliaLang#61992), because `dotted()` only handled the heads
`.= .&& .|| .op=`. Add dedicated `K".≔"`/`K".⩴"`/`K".≕"` heads -
matching `K".="` - so these parse to `(.≔ a b)` and convert to
`Expr(:.≔, a, b)` as in the reference parser. Dotted `:=` is not valid
syntax (the reference parser rejects it too), so emit a proper parse
error for it instead of crashing.

Also a few smaller items from review of JuliaLang#62000:
* The new heads also fix `Expr(:.≔, a, b)` conversion into a
  `SyntaxTree`, which previously went through JuliaLowering's compound
  assignment `unknown_head` path. That path now additionally requires a
  trailing `=` in the head name, so operator-named heads such as a
  hand-written `Expr(:⊕, a, b)` produce a lowering error rather than
  being mangled into an `op=` form.
* Add `K"Operator"` to `_nonunique_kind_names`, since the kind name
  doesn't determine the token text.
* Remove duplicated `·` (U+00B7) and `·` (U+0387) entries from the
  `PREC_TIMES` operator table (they are listed - with comments - at the
  end of the list).
* Fix an asymmetry in `Expr` conversion of one-argument `K".op="` vs
  `K"op="` heads.
* Bump the JuliaSyntaxHighlighting pin to drop a stray local-path
  `[sources]` section (left over from testing) from its Project.toml.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

We don't need 800 kinds

2 participants