Skip to content

"[A | B]" grouped-union syntax for type tags - #1702

Closed
apiology wants to merge 1 commit into
lsegal:mainfrom
apiology:issue-1699-union-grouping
Closed

"[A | B]" grouped-union syntax for type tags#1702
apiology wants to merge 1 commit into
lsegal:mainfrom
apiology:issue-1699-union-grouping

Conversation

@apiology

Copy link
Copy Markdown
Contributor

Summary

Closes #1699. There was no way to nest a union inside a position where ,
already means something else - most concretely, an order-dependent list's
positional slots (Array(A, B) already means "2 slots", so , can't also
mean "alternative for this slot" there).

[...] is a new, dedicated grouping construct - unlike <...>, (...),
and {...}, it never takes a preceding type name and never means a
collection; [A | B] alone just means "A or B", exactly like the plain
top-level list A, B, but usable anywhere a type is expected. | is only
meaningful inside [...], and , is not allowed inside [...] - the two
never compete for the same position, so there's nothing to disambiguate
(no need for a "cannot mix , and |" rule, or extra nested parens).

This resolves the motivating example from the issue directly:
Array([Integer | String], Symbol) is a 2-element tuple whose first
element is an Integer or a String, followed by a Symbol - no extra
nesting required.

Why [] and not () or reusing ,

  • () already has an established meaning (Array(A, B), an
    order-dependent tuple) that this repo isn't changing here, to leave that
    syntax free for a possible future use.
  • [ has no meaning at all today in YARD::Tags::TypesExplainer::Parser
    (only <, (, { are wired up as bracket-openers there), even though
    the outer tag-value tokenizer (DefaultFactory::TYPELIST_OPENING_CHARS)
    already balances it generically - so it's free to claim without any
    parsing-level conflict.
  • Reusing the existing ,-for-union convention inside the grouping
    brackets was considered and rejected: it would make [A, B] and
    [A | B] two spellings of the same thing, and (worse) invites
    misreading [A, B] as an array/tuple literal by analogy with other
    ecosystems, rather than as a plain 2-way alternative. | avoids both.

What else changed

Also documents three pre-existing (but previously undocumented in
docs/Tags.md) anonymous shorthand forms - <A>, (A), and {A=>B} -
where the leading type name can be omitted and defaults to Array or
Hash respectively. These were already implemented and spec-tested, and
documented externally at https://yardoc.org/types.html, but absent from
this repo's own docs; see #1701.

Test plan

  • bundle exec rspec spec/tags/types_explainer_spec.rb - added specs
    for GroupType#to_s, parser-level grouping/error cases, and end-to-end
    .explain examples.
  • bundle exec rspec - full suite green (2802 examples, 0 failures).

Closes lsegal#1699: there was no way to nest a union inside a position where
"," already means something else - most concretely, an order-dependent
list's positional slots ("Array(A, B)" already means "2 slots", so
"," can't also mean "alternative for this slot" there).

"[...]" is a new, dedicated grouping construct - unlike "<...>",
"(...)", and "{...}", it never takes a preceding type name and never
means a collection; "[A | B]" alone just means "A or B", exactly like
the plain top-level list "A, B", but usable anywhere a type is
expected. "|" is only meaningful inside "[...]", and "," is not
allowed inside "[...]" - the two never compete for the same
position, so unlike YARD's overloaded "," there is nothing to
disambiguate.

This resolves the motivating example from the issue directly, no
extra nesting required: "Array([Integer | String], Symbol)" is a
2-element tuple whose first element is an Integer or a String.

Also documents three pre-existing (but previously undocumented in
docs/Tags.md) anonymous shorthand forms - "<A>", "(A)", and "{A=>B}" -
where the leading type name can be omitted and defaults to "Array" or
"Hash" respectively. These were already implemented and spec-tested,
and documented externally at https://yardoc.org/types.html, but absent
from this repo's own docs.
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.
@apiology

Copy link
Copy Markdown
Contributor Author

Superseded by #1700, which now combines this PR's grouped-union work with the intersection-type PR into a single PR, per review feedback. Closing this one.

@apiology apiology closed this Jul 31, 2026
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.

No general grouping syntax for unions nested inside tuples, intersections, etc.

1 participant