Add intersection ("A & B") and union ("A | B" / "[A | B]") type syntax - #1700
Open
apiology wants to merge 1 commit into
Open
Add intersection ("A & B") and union ("A | B" / "[A | B]") type syntax#1700apiology wants to merge 1 commit into
apiology wants to merge 1 commit into
Conversation
apiology
added a commit
to apiology/yard
that referenced
this pull request
Jul 31, 2026
Combines intersection type support ("A & B", from lsegal#1700) with grouped-
union support ("[A | B]", from lsegal#1702) into a single PR, per review
feedback, and fixes an integration bug the merge surfaced: the
grouping syntax's "|" handler pushed its type directly instead of
routing through finish_intersection, which would have silently
dropped any pending "&" conjuncts when a group boundary was hit (e.g.
in "[Foo & Bar | Baz]").
Also fixes a real English-rendering ambiguity in the combined output:
GroupType wraps its whole member list in one pair of parens, but
IntersectionType (and a multi-method DuckType, "#foo & #bar") render
as a bare "X and Y" with no punctuation of their own. Sitting next to
a sibling in the group's "or"-joined list, that read ambiguously
("a Foo and a Bar or a Baz" doesn't show which operator binds
tighter) even though the parse itself was correct. GroupType now adds
defensive parens around exactly those two cases.
Documents operator precedence explicitly: "&" always binds tighter
than whichever separator surrounds it ("," at the top level, "|"
inside "[...]"); "|" is only valid inside "[...]"; "," is never valid
inside "[...]". Adds end-to-end specs combining both operators,
including the precedence and disambiguation cases above.
2 tasks
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
apiology
commented
Aug 1, 2026
…e override Adds three related type-tag syntax elements: - `&` (intersection, closes lsegal#1644): `Foo & Bar` means a value must satisfy both `Foo` and `Bar`, matching Solargraph's syntax (castwide/solargraph#1231). Legal in every position a type can appear, and always binds tighter than the union or slot separator around it, matching RBS's documented precedence. Renders as "both a Foo and a Bar" (or "all of a Foo, a Bar, and a Baz" for 3+), to avoid reading like two separate values. - `|` (union, closes lsegal#1699): marks a union - a value matching any of the listed types. Some type lists already mean a union without it (the top level, a hash's key/value lists, `[...]`, and `Array<...>`/`Set<...>`), so `,` and `|` land on the same result there. Elsewhere, each comma-separated item is a distinct, positional type parameter instead (a fixed-order list like `Array(...)`, or `<...>` for a name other than `Array`/`Set`) - there, `|` groups alternatives within a single one of them: `Array(Foo | Bar, Baz)` is a 2-element Array whose first element is a Foo or a Bar, and `Result<Success | Failure, Other>` is a Result whose first type parameter is a Success or a Failure. - `[...]` (closes lsegal#1699): used the same way parentheses are in algebra, to override the default order of operations - e.g. to use a union as one conjunct of an intersection, which otherwise has no way to mark where the union ends: `[Foo | Bar] & Baz`. Also documents three pre-existing but previously undocumented anonymous shorthand forms - `<A>`, `(A)`, `{A=>B}` - where the leading type name can be omitted and defaults to `Array`/`Hash` (see lsegal#1701), and stops `Foo<A, B>` from always being read as a union: `<...>`'s type parameters are conventionally used both ways - a homogeneous collection's implicit union of element type(s) (`Array<String, Symbol>`), or a class's distinct, positional type parameters (`Result<Success, Failure>`). `Array`/`Set` (and any name with a single type parameter) keep the union reading; `Hash<K, V>` gets its own dedicated key/value rendering matching `Hash{K=>V}`; anything else with 2+ parameters reads neutrally ("a Result with type parameters (a Success, a Failure)"). This choice is made entirely by `CollectionType#to_s` at render time - the parser always treats `<...>` the same way it already treats `(...)` (`,` separates positional type parameters, `|` groups alternatives within one of them), with no name-specific knowledge at all. Full rules and examples are in the new "Operator Precedence" and "Overriding the Order of Operations" sections of `docs/Tags.md`, and the rewritten "Parameterized Types"/"Union Operator" sections. Test plan: - `bundle exec rspec spec/tags/types_explainer_spec.rb` - specs for `IntersectionType`/`GroupType`/`CollectionType#to_s`, parser-level precedence/error cases, and end-to-end `.explain` examples. - `bundle exec rspec` - full suite green (2830 examples, 0 failures).
apiology
force-pushed
the
issue-1644-literal-syntax
branch
from
August 1, 2026 20:37
d78cc24 to
6521988
Compare
apiology
marked this pull request as ready for review
August 1, 2026 21:03
Contributor
Author
|
@lsegal this one is ready for review |
apiology
added a commit
to apiology/solargraph
that referenced
this pull request
Aug 1, 2026
lsegal/yard#1700 proposes standardizing `|` as an explicit union operator and `[...]` as a grouping construct for YARD type tags, alongside the `&` intersection operator this branch already added for solargraph#1229. Implementing the full syntax here so Solargraph's own parser and the upstream proposal describe the same grammar, and so `(A | B) & C` - previously only buildable by translating real RBS or constructing an Intersection object directly, per the now-outdated comment on the parentheses spec - has an actual tag-string form. `|` binds looser than `&` (matching RBS's documented precedence) and, inside a fixed-arity context (`Array(...)` tuples, or a generic type's positional parameters), groups multiple types into a single slot instead of splitting into separate positional arguments - the same distinction `,` already makes there. In an implicit-union context (Array<...>/Set<...>, hash key/value lists, the top-level list itself), `|` and `,` land on the same result, since every comma-separated type in those contexts is already unioned regardless of grouping. `[...]` is the actual grouping construct - the only way to mark where a union ends when it needs to be one conjunct of an intersection (`[Foo | Bar] & Baz`). It's deliberately conservative about when it opens: only at a fresh atom (blank base, not already nested in <>/{}/()), otherwise `[`/`]` are ordinary characters - this matters for quoted string-literal types like `"[]"`, which have no concept of grouping and would otherwise crash self-typecheck against the real Dir RBS core stub. Also fixes the anonymous shorthand forms `<A>`, `(A)`, `{A=>B}` (typed before this as an empty-name UniqueType) to default their name to Array/Array/Hash respectively, per YARD #1700's third documented change - so an anonymous form now behaves exactly like its named equivalent, including for rooting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wfsRatLaRbxcsQ7ZVzifN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three related type-tag syntax elements:
&(intersection, closes Syntax for intersection types #1644):Foo & Barmeans a value mustsatisfy both
FooandBar, matching Solargraph's syntax(Add intersection (A & B), union (|), and grouping ([...]) type syntax castwide/solargraph#1231). Legal in every position a type can appear,
and always binds tighter than the union or slot separator around it,
matching RBS's documented precedence. Renders as "both a Foo and a
Bar" (or "all of a Foo, a Bar, and a Baz" for 3+), to avoid reading
like two separate values.
|(union, closes No general grouping syntax for unions nested inside tuples, intersections, etc. #1699): marks a union - a value matching any ofthe listed types. Some type lists already mean a union without it (the
top level, a hash's key/value lists,
[...], andArray<...>/Set<...>), so,and|land on the same result there.Elsewhere, each comma-separated item is a distinct, positional type
parameter instead (a fixed-order list like
Array(...), or<...>fora name other than
Array/Set) - there,|groups alternativeswithin a single one of them:
Array(Foo | Bar, Baz)is a 2-elementArray whose first element is a Foo or a Bar, and
Result<Success | Failure, Other>is a Result whose first typeparameter is a Success or a Failure.
[...](closes No general grouping syntax for unions nested inside tuples, intersections, etc. #1699): used the same way parentheses are inalgebra, to override the default order of operations - e.g. to use a
union as one conjunct of an intersection, which otherwise has no way to
mark where the union ends:
[Foo | Bar] & Baz.Also documents three pre-existing but previously undocumented anonymous
shorthand forms -
<A>,(A),{A=>B}- where the leading type name canbe omitted and defaults to
Array/Hash(see #1701), and stopsFoo<A, B>from always being read as a union:<...>'s type parametersare conventionally used both ways - a homogeneous collection's implicit
union of element type(s) (
Array<String, Symbol>), or a class'sdistinct, positional type parameters (
Result<Success, Failure>).Array/Set(and any name with a single type parameter) keep the unionreading;
Hash<K, V>gets its own dedicated key/value rendering matchingHash{K=>V}; anything else with 2+ parameters reads neutrally ("a Resultwith type parameters (a Success, a Failure)"). This choice is made
entirely at render time (
CollectionType#to_s) - the parser alwaystreats
<...>the same way it already treats(...), with noname-specific knowledge at all.
Full rules and examples are in the new "Operator Precedence" and
"Overriding the Order of Operations" sections of
docs/Tags.md, and therewritten "Parameterized Types"/"Union Operator" sections.
Test plan
bundle exec rspec spec/tags/types_explainer_spec.rb- specs forIntersectionType/GroupType/CollectionType#to_s, parser-levelprecedence/error cases, and end-to-end
.explainexamples.bundle exec rspec- full suite green (2830 examples, 0 failures).