union-type 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edfcd1ae4c61f2097f644de21b2403419afc6c121d751823de6018a00975b921
4
- data.tar.gz: a70a742afc27d29e14b62f420f00cee5bd97b8a97a736a577a566846e422af0c
3
+ metadata.gz: df57236a684d1ebd848590bf182f721c445dc23e3baeaa0b64e94fdccf8eac5b
4
+ data.tar.gz: 98a0c1d10cc84f2288cb9f19c573b1d180fd548d0c7a6fbfda3a317b9db188ad
5
5
  SHA512:
6
- metadata.gz: ce9eaa5d32bf49436f52697773c6ab38aadbda78f27749602d8dda4b13ee87e6f3b5a7fa0c232df7b5a9444257b9304297a0d7401d1b09b04baf1f1cac42d597
7
- data.tar.gz: e3f0954b10a66a9577ae019017c607def1d9af198d2679211c592d0a26416d158510ea5ee7b5539c891dc41c103a9ec62b21611a74165b0afaaf0bb6b0cc35d5
6
+ metadata.gz: bcbda94716e0b6556ca8c7933ebfc3e0ed89eb237045485f6d1124e312b3bb889afa2f709f09c43ef028e1ad06378ea92c24642c796326076eb0fc50c42c03bf
7
+ data.tar.gz: cff63507e647111edef948440d1f0a959f37070c6d136e2d0931cfc9ab6b05196b65a306b1fd92c69964bdef758fedfd82f381be821aeec1721872d5e77817ca
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bogdan Gusiev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,4 +1,14 @@
1
1
  class Class
2
+ # Returns a {UnionType} combining +self+ and +other+.
3
+ # Enables the +String | Integer+ literal syntax.
4
+ #
5
+ # @param other [Class, UnionType]
6
+ # @return [UnionType]
7
+ # @raise [TypeError] if +other+ is not a {Class} or {UnionType}
8
+ #
9
+ # @example
10
+ # String | Integer # => UnionType(Integer | String)
11
+ # String | Integer | Float # => UnionType(Float | Integer | String)
2
12
  def |(other)
3
13
  case other
4
14
  when Class
@@ -15,11 +25,31 @@ class Object
15
25
  alias_method :__is_a__?, :is_a?
16
26
  alias_method :__instance_of__?, :instance_of?
17
27
 
28
+ # Returns +true+ if this object is an instance of any class in the union.
29
+ # Falls back to the standard +is_a?+ for plain {Class} or {Module} arguments.
30
+ #
31
+ # @param type [Class, Module, UnionType]
32
+ # @return [Boolean]
33
+ #
34
+ # @example
35
+ # "hello".is_a?(String | Integer) # => true
36
+ # 42.is_a?(String | Integer) # => true
37
+ # :sym.is_a?(String | Integer) # => false
18
38
  def is_a?(type)
19
39
  type.__is_a__?(UnionType) ? type === self : __is_a__?(type)
20
40
  end
21
41
  alias kind_of? is_a?
22
42
 
43
+ # Returns +true+ if this object's exact class is one of the union's members.
44
+ # Unlike {#is_a?}, subclass membership does not satisfy this check.
45
+ # Falls back to the standard +instance_of?+ for plain {Class} arguments.
46
+ #
47
+ # @param type [Class, UnionType]
48
+ # @return [Boolean]
49
+ #
50
+ # @example
51
+ # 42.instance_of?(Integer | String) # => true
52
+ # 42.instance_of?(Numeric | String) # => false (42 is not exactly Numeric)
23
53
  def instance_of?(type)
24
54
  type.__is_a__?(UnionType) ? type.include?(self.class) : __instance_of__?(type)
25
55
  end
@@ -1,8 +1,29 @@
1
1
  require "sorted_set"
2
2
 
3
+ # A union type that matches values belonging to any of its member classes.
4
+ #
5
+ # Member classes are stored in a {SortedSet} sorted alphabetically by name.
6
+ # Redundant subclasses are dropped automatically: if a superclass is already
7
+ # present, its subclasses add no information and are removed.
8
+ #
9
+ # @example Basic usage
10
+ # union = String | Integer # => UnionType(Integer | String)
11
+ # union === "hello" # => true
12
+ # union === 42 # => true
13
+ # union === :sym # => false
14
+ #
15
+ # @example case/when
16
+ # case value
17
+ # when String | Integer then "string or int"
18
+ # when Float then "float"
19
+ # end
20
+ #
21
+ # @example Superclass deduplication
22
+ # UnionType[Integer, Float, Numeric] # => UnionType(Numeric)
3
23
  class UnionType
4
24
  include Enumerable
5
25
 
26
+ # @private
6
27
  class Entry
7
28
  include Comparable
8
29
 
@@ -13,33 +34,81 @@ class UnionType
13
34
  end
14
35
  private_constant :Entry
15
36
 
37
+ # Creates a frozen {UnionType} from the given classes.
38
+ # Equivalent to {.new} but reads more naturally as a literal.
39
+ #
40
+ # @param classes [Array<Class>] one or more classes
41
+ # @return [UnionType]
42
+ #
43
+ # @example
44
+ # UnionType[String, Integer] # => UnionType(Integer | String)
16
45
  def self.[](*classes) = new(*classes)
17
46
 
47
+ # @param classes [Array<Class>] one or more classes to include in the union.
48
+ # Subclasses of other members are dropped automatically.
49
+ # @raise [ArgumentError] if no classes are given
18
50
  def initialize(*classes)
19
51
  raise ArgumentError, "requires at least one class" if classes.empty?
20
52
 
21
53
  minimal = classes.reject { |c| classes.any? { |other| c != other && c < other } }
22
- @types = SortedSet.new(minimal.map { Entry.new(_1) })
54
+ @types = SortedSet.new(minimal.map { Entry.new(_1) }).freeze
55
+ freeze
23
56
  end
24
57
 
58
+ # Yields each member class in sorted (alphabetical) order.
59
+ # Required by {Enumerable}; enables {#to_a}, {#map}, {#include?}, etc.
60
+ #
61
+ # @yieldparam klass [Class]
62
+ # @return [self]
25
63
  def each(&block)
26
64
  @types.each { block.call(_1.klass) }
27
65
  end
28
66
 
29
- # Supports case/when and the === operator.
67
+ # Returns +true+ if +value+ is an instance of any member class.
68
+ # This makes {UnionType} work in +case+/+when+ expressions.
69
+ #
70
+ # @param value [Object]
71
+ # @return [Boolean]
72
+ #
73
+ # @example
74
+ # (String | Integer) === "hi" # => true
75
+ # (String | Integer) === :sym # => false
30
76
  def ===(value)
31
77
  any? { _1 === value }
32
78
  end
33
79
 
34
- # The underlying SortedSet of Entry objects (sorted by class name).
80
+ # The underlying {SortedSet} of internal entry objects.
81
+ # Entries are sorted by class name; use {#to_a} to get plain {Class} objects.
82
+ #
83
+ # @return [SortedSet]
35
84
  def types
36
85
  @types
37
86
  end
38
87
 
88
+ # Returns +true+ if this union covers +klass+ — i.e. +klass+ is a member or
89
+ # a subclass of a member.
90
+ #
91
+ # @param klass [Class]
92
+ # @return [Boolean]
93
+ #
94
+ # @example
95
+ # (Numeric | String).cover?(Integer) # => true (Integer < Numeric)
96
+ # (Numeric | String).cover?(Numeric) # => true (exact member)
97
+ # (Integer | String).cover?(Numeric) # => false (superclass, not covered)
39
98
  def cover?(klass)
40
99
  any? { klass <= _1 }
41
100
  end
42
101
 
102
+ # Returns a new {UnionType} that is the union of +self+ and +other+.
103
+ # Redundant subclasses are removed as in {.new}.
104
+ #
105
+ # @param other [Class, UnionType]
106
+ # @return [UnionType]
107
+ # @raise [TypeError] if +other+ is not a {Class} or {UnionType}
108
+ #
109
+ # @example
110
+ # (String | Integer) | Float # => UnionType(Float | Integer | String)
111
+ # [String, Integer, Float].reduce(:|) # => UnionType(Float | Integer | String)
43
112
  def |(other)
44
113
  case other
45
114
  when Class then UnionType.new(*self, other)
@@ -48,8 +117,18 @@ class UnionType
48
117
  end
49
118
  end
50
119
 
51
- # Returns a new UnionType covering only values matched by both unions,
52
- # or nil when the intersection is empty.
120
+ # Returns a new {UnionType} containing only classes matched by *both* unions,
121
+ # keeping the more specific class when one is a subclass of the other.
122
+ # Returns +nil+ when the intersection is empty.
123
+ #
124
+ # @param other [Class, UnionType]
125
+ # @return [UnionType, nil]
126
+ # @raise [TypeError] if +other+ is not a {Class} or {UnionType}
127
+ #
128
+ # @example
129
+ # (String | Integer) & (Integer | Float) # => UnionType(Integer)
130
+ # (String | Integer) & Numeric # => UnionType(Integer)
131
+ # (String | Integer) & Float # => nil
53
132
  def &(other)
54
133
  other_types = case other
55
134
  when UnionType then other.to_a
@@ -68,15 +147,22 @@ class UnionType
68
147
  UnionType.new(*classes) unless classes.empty?
69
148
  end
70
149
 
150
+ # Value equality: two unions are equal when they contain the same classes.
151
+ # Argument order does not matter.
152
+ #
153
+ # @param other [Object]
154
+ # @return [Boolean]
71
155
  def ==(other)
72
156
  other.class == UnionType && to_a == other.to_a
73
157
  end
74
158
  alias eql? ==
75
159
 
160
+ # @return [Integer] hash consistent with {#eql?}, for use as a Hash key
76
161
  def hash
77
162
  to_a.hash
78
163
  end
79
164
 
165
+ # @return [String] human-readable representation, e.g. +UnionType(Integer | String)+
80
166
  def inspect
81
167
  "UnionType(#{map(&:name).join(" | ")})"
82
168
  end
@@ -1,3 +1,5 @@
1
1
  class UnionType
2
- VERSION = "0.1.0"
2
+ # The current gem version.
3
+ # @return [String]
4
+ VERSION = "0.1.1"
3
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: union-type
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bogdan Gusiev
@@ -58,13 +58,14 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
+ - LICENSE
61
62
  - README.md
62
63
  - lib/union-type-no-ext.rb
63
64
  - lib/union-type.rb
64
65
  - lib/union_type/core_ext.rb
65
66
  - lib/union_type/union_type.rb
66
67
  - lib/union_type/version.rb
67
- homepage: https://github.com/bogdangusiev/union-type
68
+ homepage: https://github.com/bogdan/ruby-union-type
68
69
  licenses:
69
70
  - MIT
70
71
  metadata: {}