@@ -538,12 +538,17 @@ def parse_type_string type_string, types, key_types
538538 point_stack = 0
539539 curly_stack = 0
540540 paren_stack = 0
541+ bracket_stack = 0
541542 base = String . new
542543 subtype_string = String . new
543544 # conjuncts of an intersection type (`A & B`) seen so far in
544- # the segment currently being parsed
545+ # the current `|`-disjunct of the segment being parsed
545546 # @type [Array<ComplexType>]
546547 conjuncts = [ ]
548+ # disjuncts of a union type (`A | B`) seen so far in the
549+ # segment currently being parsed
550+ # @type [Array<ComplexType, ComplexType::UniqueType>]
551+ disjuncts = [ ]
547552 # @param char [String]
548553 type_string &.each_char do |char |
549554 if char == '='
@@ -555,17 +560,8 @@ def parse_type_string type_string, types, key_types
555560 subtype_string += char
556561 elsif base . end_with? ( '=' )
557562 raise ComplexTypeError , 'Invalid hash thing' unless key_types . nil?
558- # types.push ComplexType.new([UniqueType.new(base[0..-2].strip)])
559- # @sg-ignore Need to add nil check here
560- types . push close_intersection ( conjuncts , UniqueType . parse ( base [ 0 ..-2 ] . strip , subtype_string ) )
561- # @todo this should either expand key_type's type
562- # automatically or complain about not being
563- # compatible with key_type's type in type checking
564- key_types = types
563+ key_types = close_key_types ( base , subtype_string , conjuncts , disjuncts , types )
565564 types = [ ]
566- conjuncts = [ ]
567- base . clear
568- subtype_string . clear
569565 next
570566 else
571567 raise ComplexTypeError , "Invalid close in type #{ type_string } " if point_stack . zero?
@@ -576,66 +572,168 @@ def parse_type_string type_string, types, key_types
576572 elsif char == '{'
577573 curly_stack += 1
578574 elsif char == '}'
579- curly_stack -= 1
580- subtype_string += char
581- raise ComplexTypeError , "Invalid close in type #{ type_string } " if curly_stack . negative?
575+ curly_stack = close_bracket ( curly_stack , subtype_string , char , type_string )
582576 next
583577 elsif char == '('
584578 paren_stack += 1
585579 elsif char == ')'
586- paren_stack -= 1
587- subtype_string += char
588- raise ComplexTypeError , "Invalid close in type #{ type_string } " if paren_stack . negative?
580+ paren_stack = close_bracket ( paren_stack , subtype_string , char , type_string )
589581 next
590- elsif char == '&' && top_level? ( point_stack , curly_stack , paren_stack )
591- conjuncts . push ComplexType . new ( [ UniqueType . parse ( base . strip , subtype_string . strip ) ] )
582+ elsif char == '[' &&
583+ ( bracket_stack . positive? ||
584+ ( base . strip . empty? && point_stack . zero? && curly_stack . zero? && paren_stack . zero? ) )
585+ # Only a fresh atom (blank base, not already nested in
586+ # <>/{}/()) can start a `[...]` group - matching
587+ # finish_atom's own precondition. Otherwise `[` is just an
588+ # ordinary character, e.g. part of a quoted string literal
589+ # type like `"[]"`, which has no concept of grouping.
590+ bracket_stack += 1
591+ elsif char == ']' && bracket_stack . positive?
592+ bracket_stack = close_bracket ( bracket_stack , subtype_string , char , type_string )
593+ next
594+ elsif char == '&' && top_level? ( point_stack , curly_stack , paren_stack , bracket_stack )
595+ conjuncts . push ComplexType . new ( [ finish_atom ( base , subtype_string ) ] )
596+ base . clear
597+ subtype_string . clear
598+ next
599+ elsif char == '|' && top_level? ( point_stack , curly_stack , paren_stack , bracket_stack )
600+ disjuncts . push close_intersection ( conjuncts , finish_atom ( base , subtype_string ) )
601+ conjuncts = [ ]
592602 base . clear
593603 subtype_string . clear
594604 next
595- elsif char == ',' && top_level? ( point_stack , curly_stack , paren_stack )
596- # types .push ComplexType.new([UniqueType.new (base.strip , subtype_string.strip)] )
597- types . push close_intersection ( conjuncts , UniqueType . parse ( base . strip , subtype_string . strip ) )
605+ elsif char == ',' && top_level? ( point_stack , curly_stack , paren_stack , bracket_stack )
606+ disjuncts . push close_intersection ( conjuncts , finish_atom ( base , subtype_string ) )
607+ types . push close_disjunction ( disjuncts )
598608 conjuncts = [ ]
609+ disjuncts = [ ]
599610 base . clear
600611 subtype_string . clear
601612 next
602613 end
603- if top_level? ( point_stack , curly_stack , paren_stack )
614+ if top_level? ( point_stack , curly_stack , paren_stack , bracket_stack )
604615 base . concat char
605616 else
606617 subtype_string . concat char
607618 end
608619 end
609- if point_stack != 0 || curly_stack != 0 || paren_stack != 0
620+ if point_stack != 0 || curly_stack != 0 || paren_stack != 0 || bracket_stack != 0
610621 raise ComplexTypeError ,
611622 "Unclosed subtype in #{ type_string } "
612623 end
613- # types .push ComplexType.new([UniqueType.new (base, subtype_string)] )
614- types . push close_intersection ( conjuncts , UniqueType . parse ( base . strip , subtype_string . strip ) )
624+ disjuncts . push close_intersection ( conjuncts , finish_atom ( base , subtype_string ) )
625+ types . push close_disjunction ( disjuncts )
615626 [ types , key_types ]
616627 end
617628
629+ # Decrements the stack counter for a closing `}`/`)`/`]` and
630+ # appends it to the pending subtype substring.
631+ #
632+ # @param stack [Integer]
633+ # @param subtype_string [String]
634+ # @param char [String]
635+ # @param type_string [String, nil]
636+ # @return [Integer] the decremented stack counter
637+ def close_bracket stack , subtype_string , char , type_string
638+ stack -= 1
639+ subtype_string << char
640+ raise ComplexTypeError , "Invalid close in type #{ type_string } " if stack . negative?
641+ stack
642+ end
643+
644+ # Closes the key-list portion of a `Hash{K=>V}` split (the base
645+ # ending in `=` marks the boundary) and returns the types parsed
646+ # so far to be stashed as the eventual key_types, leaving
647+ # conjuncts/disjuncts/base/subtype_string cleared for the value
648+ # list that follows.
649+ #
650+ # @todo this should either expand key_type's type automatically
651+ # or complain about not being compatible with key_type's type
652+ # in type checking
653+ #
654+ # @param base [String]
655+ # @param subtype_string [String]
656+ # @param conjuncts [Array<ComplexType>]
657+ # @param disjuncts [Array<ComplexType, ComplexType::UniqueType>]
658+ # @param types [Array<ComplexType::UniqueType, ComplexType>]
659+ # @return [Array<ComplexType::UniqueType, ComplexType>] the key_types
660+ def close_key_types base , subtype_string , conjuncts , disjuncts , types
661+ # @sg-ignore Need to add nil check here
662+ disjuncts . push close_intersection ( conjuncts , finish_atom ( base [ 0 ..-2 ] , subtype_string ) )
663+ types . push close_disjunction ( disjuncts )
664+ conjuncts . clear
665+ disjuncts . clear
666+ base . clear
667+ subtype_string . clear
668+ types
669+ end
670+
618671 # @param point_stack [Integer]
619672 # @param curly_stack [Integer]
620673 # @param paren_stack [Integer]
674+ # @param bracket_stack [Integer]
621675 # @return [Boolean]
622- def top_level? point_stack , curly_stack , paren_stack
623- point_stack . zero? && curly_stack . zero? && paren_stack . zero?
676+ def top_level? point_stack , curly_stack , paren_stack , bracket_stack
677+ point_stack . zero? && curly_stack . zero? && paren_stack . zero? && bracket_stack . zero?
624678 end
625679
626- # Wraps a just-parsed unique type together with any pending
627- # intersection conjuncts (types seen so far in this segment,
680+ # Resolves one type atom - either an ordinary named type (`base`
681+ # plus its optional `<...>`/`(...)`/`{...}` parameter substring),
682+ # or a standalone `[...]` grouping with no leading name, used to
683+ # override the default order of operations (e.g. `[Foo | Bar] &
684+ # Baz`, where `[...]` is the only way to mark where the union
685+ # ends). A bracket group's content is parsed the same way any
686+ # other parameter substring is - recursively, via
687+ # ComplexType.parse - and its result substituted directly, since
688+ # it can itself be a multi-item union (or an intersection).
689+ #
690+ # @param base [String]
691+ # @param subtype_string [String]
692+ # @return [ComplexType::UniqueType, ComplexType]
693+ def finish_atom base , subtype_string
694+ base = base . strip
695+ subtype_string = subtype_string . strip
696+ if base . empty? && subtype_string . start_with? ( '[' )
697+ raise ComplexTypeError , "Unclosed bracket group in #{ subtype_string } " unless subtype_string . end_with? ( ']' )
698+ return ComplexType . new ( ComplexType . parse ( subtype_string [ 1 ..-2 ] , partial : true ) )
699+ end
700+ UniqueType . parse ( base , subtype_string )
701+ end
702+
703+ # Wraps a just-parsed atom together with any pending
704+ # intersection conjuncts (types seen so far in this disjunct,
628705 # separated by `&`) into a single UniqueType. Each conjunct is
629706 # a ComplexType (see UniqueType::Intersection), so the final
630707 # parsed type is promoted to a single-item ComplexType too.
631708 #
632709 # @param conjuncts [Array<ComplexType>]
633- # @param final_type [ComplexType::UniqueType]
634- # @return [ComplexType::UniqueType]
710+ # @param final_type [ComplexType::UniqueType, ComplexType ]
711+ # @return [ComplexType::UniqueType, ComplexType ]
635712 def close_intersection conjuncts , final_type
636713 return final_type if conjuncts . empty?
637714 UniqueType ::Intersection . new ( conjuncts + [ ComplexType . new ( [ final_type ] ) ] )
638715 end
716+
717+ # Collapses the disjuncts of a union type (`A | B`) seen so far
718+ # in the segment currently being parsed into a single value to
719+ # push into the enclosing types/subtypes list - a bare type when
720+ # there was only one (the common case, `|` never used), or a
721+ # real multi-item ComplexType union otherwise. This is also
722+ # exactly what a top-level `,` in an already-implicit-union
723+ # context (Array<...>, Set<...>, hash key/value lists, the
724+ # top-level types list itself) reduces to, since each of those
725+ # contexts flattens every comma-separated type into one union
726+ # regardless of how it's grouped here - so `,` and `|` land on
727+ # the same result there, matching RBS's own tag design.
728+ #
729+ # @param disjuncts [Array<ComplexType, ComplexType::UniqueType>]
730+ # @return [ComplexType::UniqueType, ComplexType]
731+ # @sg-ignore #first is only nil for an empty array, and this is
732+ # never called with one
733+ def close_disjunction disjuncts
734+ return disjuncts . first if disjuncts . length == 1
735+ ComplexType . new ( disjuncts )
736+ end
639737 end
640738
641739 VOID = ComplexType . parse ( 'void' )
0 commit comments