@@ -123,20 +123,48 @@ def disambiguate(type, singular)
123123 end
124124
125125 # @private
126+ #
127+ # `<...>`'s type parameters are conventionally used two ways - as a
128+ # homogeneous collection's implicit union of element type(s), or as
129+ # a class's distinct, positional type parameters, e.g.
130+ # `Result<Success, Failure>` - and this class renders either way,
131+ # chosen per instance by {#union?}. This keeps that choice entirely
132+ # out of the parser: every `<...>` becomes one of these, regardless
133+ # of name.
126134 class CollectionType < Type
127135 attr_accessor :types
128136
137+ # Type names known in advance to be genuinely homogeneous
138+ # collections, where `<...>`'s type parameters really do mean
139+ # "any one of these". Fixed and not user-configurable - YARD has
140+ # no syntax for a class to declare its own `<...>` convention, so
141+ # this can only ever be a hardcoded, conservative list.
142+ UNION_COLLECTION_NAMES = %w[ Array Set ] . freeze
143+
129144 def initialize ( name , types )
130145 @name = name
131146 @types = types
132147 end
133148
134149 def to_s ( _singular = true )
135- "#{ indefinite_article ( name ) } #{ name } of (" + list_join ( flattened_types . map { |t | t . to_s ( false ) } ) + ")"
150+ if union?
151+ "#{ indefinite_article ( name ) } #{ name } of (" + list_join ( flattened_types . map { |t | t . to_s ( false ) } ) + ")"
152+ else
153+ "#{ indefinite_article ( name ) } #{ name } with type parameters (" +
154+ types . map { |t | t . to_s ( true ) } . join ( ", " ) + ")"
155+ end
136156 end
137157
138158 private
139159
160+ # A single type parameter is never ambiguous (there's nothing to
161+ # distinguish a union from a positional type parameter when
162+ # there's only one), so it's read as a union regardless of name;
163+ # otherwise, only the known homogeneous-collection names are.
164+ def union?
165+ types . size <= 1 || UNION_COLLECTION_NAMES . include? ( name )
166+ end
167+
140168 # A parsed type parameter that's itself a union (from `Foo | Bar`
141169 # in e.g. `Array<Foo | Bar, Baz>`) is a {GroupType}. Since this
142170 # whole parameter list is already read as one flat union, absorb
@@ -148,29 +176,6 @@ def flattened_types
148176 end
149177 end
150178
151- # @private
152- #
153- # Unlike {CollectionType}, this doesn't assert that its type
154- # parameters are alternatives ("of (A's or B's)") - `<...>` is
155- # conventionally used both ways (a homogeneous collection's element
156- # type(s), or a class's distinct positional type-parameter roles,
157- # e.g. `Result<Success, Failure>`), and there's no way for YARD to
158- # know which one a given class means. This is the honest fallback
159- # for any name not specifically known to mean the former.
160- class ParameterizedType < Type
161- attr_accessor :types
162-
163- def initialize ( name , types )
164- @name = name
165- @types = types
166- end
167-
168- def to_s ( _singular = true )
169- "#{ indefinite_article ( name ) } #{ name } with type parameters (" +
170- types . map { |t | t . to_s ( true ) } . join ( ", " ) + ")"
171- end
172- end
173-
174179 # @private
175180 class FixedCollectionType < CollectionType
176181 def to_s ( _singular = true )
@@ -258,13 +263,6 @@ class Parser
258263 :parse_end => nil
259264 }
260265
261- # Type names known in advance to be genuinely homogeneous
262- # collections, where `<...>`'s comma-separated slots really do mean
263- # "any one of these". Fixed and not user-configurable - YARD has no
264- # syntax for a class to declare its own `<...>` convention, so this
265- # can only ever be a hardcoded, conservative list.
266- UNION_COLLECTION_NAMES = %w[ Array Set ] . freeze
267-
268266 def self . parse ( string )
269267 new ( string ) . parse
270268 end
@@ -377,18 +375,12 @@ def parse_until(until_tokens, slot_pipe: false)
377375 # (slot 0 = key type, slot 1 = value type), matching the
378376 # dedicated `Hash{K=>V}` syntax - not an implicit union.
379377 HashCollectionType . new ( name , [ nested_types [ 0 ] ] , [ nested_types [ 1 ] ] )
380- elsif nested_types . size <= 1 || UNION_COLLECTION_NAMES . include? ( name )
381- # A single slot is never ambiguous (nothing to distinguish
382- # union from positional with only one type), and these
383- # names are known, genuinely homogeneous collections.
384- CollectionType . new ( name , nested_types )
385378 else
386- # `<...>` is conventionally used both ways - a homogeneous
387- # collection's element type(s), or a class's distinct
388- # positional type-parameter roles - and YARD has no way to
389- # know which one an arbitrary class means. Don't assert
390- # union for a name we don't specifically know means that.
391- ParameterizedType . new ( name , nested_types )
379+ # Whether these type parameters get described as a union
380+ # (Array, Set) or neutrally (everything else) doesn't
381+ # affect parsing, so it's not decided here - see
382+ # {CollectionType#union?}.
383+ CollectionType . new ( name , nested_types )
392384 end
393385 when :group_start
394386 raise SyntaxError , "'[' cannot follow a type name" if name
0 commit comments