···439439 )
440440 end
441441442442+ # Matches if the value matches any of the given tagged types. Tags are used to identify which type was matched.
443443+ def _TaggedUnion(**members)
444444+ TaggedUnionType.new(**members)
445445+ end
446446+447447+ # Nilable version of `_TaggedUnion`
448448+ def _TaggedUnion?(...)
449449+ _Nilable(
450450+ _TaggedUnion(...)
451451+ )
452452+ end
453453+442454 # Matches if *any* given type is matched.
443455 def _Union(*types)
444456 UnionType.new(types)
+61
lib/literal/types/tagged_union_type.rb
···11+# frozen_string_literal: true
22+33+class Literal::Types::TaggedUnionType
44+ include Literal::Type
55+66+ def initialize(**members)
77+ raise Literal::ArgumentError.new("_TaggedUnion type must have at least one member.") if members.empty?
88+99+ flattened = {}
1010+ members.each do |tag, type|
1111+ if Literal::Types::TaggedUnionType === type
1212+ type.members.each do |inner_tag, inner_type|
1313+ raise Literal::ArgumentError.new("_TaggedUnion has duplicate tag: #{inner_tag.inspect}") if flattened.key?(inner_tag)
1414+ flattened[inner_tag] = inner_type
1515+ end
1616+ else
1717+ raise Literal::ArgumentError.new("_TaggedUnion has duplicate tag: #{tag.inspect}") if flattened.key?(tag)
1818+ flattened[tag] = type
1919+ end
2020+ end
2121+2222+ @members = flattened.freeze
2323+ freeze
2424+ end
2525+2626+ attr_reader :members
2727+2828+ def inspect
2929+ pairs = @members.map { |tag, type| "#{tag}: #{type.inspect}" }
3030+ "_TaggedUnion(#{pairs.join(', ')})"
3131+ end
3232+3333+ def ===(value)
3434+ @members.any? { |_, type| type === value }
3535+ end
3636+3737+ def [](tag)
3838+ @members[tag]
3939+ end
4040+4141+ def tag_for(value)
4242+ @members.each { |tag, type| return tag if type === value }
4343+ nil
4444+ end
4545+4646+ def >=(other)
4747+ types = @members.values
4848+4949+ case other
5050+ when Literal::Types::TaggedUnionType
5151+ other.members.values.all? { |t| types.any? { |t2| Literal.subtype?(t, t2) } }
5252+ when Literal::Types::UnionType
5353+ other.types.all? { |t| types.any? { |t2| Literal.subtype?(t, t2) } } &&
5454+ other.primitives.all? { |p| types.any? { |t| Literal.subtype?(p, t) } }
5555+ else
5656+ types.any? { |t| Literal.subtype?(other, t) }
5757+ end
5858+ end
5959+6060+ freeze
6161+end