Skip to content

Add the pipe operator: a | f(x) compiles as f(a, x)#28

Merged
myzie merged 1 commit into
mainfrom
pipe-operator
Jun 12, 2026
Merged

Add the pipe operator: a | f(x) compiles as f(a, x)#28
myzie merged 1 commit into
mainfrom
pipe-operator

Conversation

@myzie

@myzie myzie commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Implements RFC 0001 (roadmap item 7). The pipe operator is always on: | was previously rejected at Compile time as an unsupported bitwise operator, so no previously-compilable expression changes meaning.

checks | filter(!it.ok) | map(sprintf("- %s: %s", it.name, it.msg)) | join("\n")
// identical to:
join(map(filter(checks, !it.ok), sprintf("- %s: %s", it.name, it.msg)), "\n")

Design

  • Compile-time desugar. desugarPipes (new pipe.go) rewrites every a | f(x, y) into f(a, x, y) between parsing and validation. The evaluator, Identifiers(), the suggester, and the Add evaluator fast paths: cached field plans, reflect-space index chains, typed binary ops #26 fast paths only ever see ordinary call nodes; the pipe has no runtime representation. Special forms compose for free, including the three-arg named-binding shape (orders | filter(o, o.paid)) and lazy try/if.
  • Non-call right sides are ErrCompile with the RFC §6 error taxonomy:
    • xs | foo"foo" is not a call (did you mean to write foo(...)?)
    • xs | filter"filter" is a special form, did you mean to write filter(predicate)? (signature shown with the collection argument dropped, since the pipe supplies it)
    • xs | a?.b → rejected like any other non-call right side (the optaccess sentinel call is detected so it cannot silently become a 3-arg sentinel)
  • Ambiguity diagnostic (RFC §3.3). A bare pipe as the right operand of a comparison fails compilation: a == b | f() parses as a == f(b) under Go precedence, so expr demands parentheses rather than silently mis-grouping. A pipe on the left (xs | count(it > 1) == 2) is the useful order and needs none.
  • Precedence is Go's: level 4, same as +/-, tighter than comparisons and logicals. Documented with worked examples in the spec.

Deviation from the RFC's recommendation

RFC §7.3 recommended shipping behind an opt-in WithPipeOperator(). Per maintainer decision, the operator is enabled unconditionally instead; the RFC's status header records the override and the rationale (the token never compiled, so the reuse breaks nothing). The §3.3 ambiguity diagnostic and §5/§6 error texts shipped as recommended.

Tests

  • New pipe_test.go: rewrite semantics, chaining, all eight iterating forms plus try/if, named bindings, precedence table, ambiguity diagnostic (fires/does-not-fire/parenthesized), error taxonomy, optional-access composition, env callables, templates, Identifiers(), Source(), piped-chain equivalence to nested calls, and a MaxEvalDepth chain bound
  • Fuzz corpus extended with pipe seeds; FuzzCompile/FuzzEval run clean
  • Docs: spec (operator table, new Pipeline section, supported/rejected lists), README, llms.txt, examples guide ([codex] Fix struct tag ambiguity tests for vet #13), higher-order patterns guide, all with matching doc tests; cmd/expr usage example

🤖 Generated with Claude Code

Implements RFC 0001 (docs/rfcs/0001-pipe-operator.md). The pipe is
always on: `|` previously rejected at Compile time as an unsupported
bitwise operator, so no compilable expression changes meaning.

- desugarPipes (pipe.go) rewrites every `a | f(x, y)` into `f(a, x, y)`
  between parsing and validation, so the evaluator, Identifiers(), and
  the fast paths only ever see ordinary call nodes
- Non-call right sides are ErrCompile with the RFC's error taxonomy,
  including "did you mean f(...)?" for bare identifiers and
  pipe-adjusted signatures for special forms (filter(predicate))
- A bare pipe as the right operand of a comparison is the RFC 3.3
  ambiguity diagnostic: `a == b | f()` demands parentheses instead of
  silently parsing as `a == f(b)`
- Optional access composes: `user?.name | upper()` pipes the optional
  result; `xs | a?.b` is rejected as a non-call right side
- Spec, README, llms.txt, examples guide (#13), higher-order patterns
  guide, and their doc tests updated; fuzz corpus extended with pipe
  seeds; RFC status updated to implemented (always on)

Co-Authored-By: Claude Fable 5 <[email protected]>
@myzie myzie merged commit 5256d92 into main Jun 12, 2026
1 check passed
@myzie myzie deleted the pipe-operator branch June 12, 2026 19:52
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.

1 participant