enummify 0.1.0 → 0.2.0
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 +4 -4
- data/lib/enummify/enum.rb +46 -5
- data/lib/enummify/enum_hash.rb +330 -0
- data/lib/enummify/enum_set.rb +314 -0
- data/lib/enummify/version.rb +1 -1
- data/lib/enummify.rb +2 -0
- data/rbi/enummify.rbi +256 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18c5b92dafe4aae8f25c5389749bbf671cf17facc883ec20da917d3f8750f786
|
|
4
|
+
data.tar.gz: f7becca7d6149d08d7be46e67a541117e11ab892258b16286d9820b50eb48e29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 55776b3e75d317357a17c8985571e98df46b3c99647de0f4ebc7ec2481a1b35cf68cf567342defcc18bad39b016f0d21231cc94914fa176021bcd5f0024d4792
|
|
7
|
+
data.tar.gz: 0502c8821c91c4157658ebe143ac7936e9a2d542a33d2eeb6feb7c382885c2551457b31eb0bc43fe040d2951c9993cb5c3d7b440de394b3a5f0c2c92bb18f6d2
|
data/lib/enummify/enum.rb
CHANGED
|
@@ -7,13 +7,15 @@ module Enummify
|
|
|
7
7
|
@serialized_to_value = {} #: Hash[String, Enum]
|
|
8
8
|
@values = nil #: Array[Enum]?
|
|
9
9
|
|
|
10
|
-
# Register
|
|
10
|
+
# Register each constant as a member, which assumes every constant in the class body is one.
|
|
11
|
+
# Groupings such as sets belong outside the enum class.
|
|
11
12
|
#: (Symbol) -> void
|
|
12
13
|
def self.const_added(constant)
|
|
13
14
|
super
|
|
14
15
|
|
|
15
|
-
member = const_get(constant, false)
|
|
16
|
-
|
|
16
|
+
member = const_get(constant, false)
|
|
17
|
+
# The registry size is the member's declaration index, which EnumSet and EnumHash rely on.
|
|
18
|
+
serialized = member.send(:finalize, constant, @serialized_to_value.size) #: as String
|
|
17
19
|
if @serialized_to_value.key?(serialized)
|
|
18
20
|
raise ArgumentError, "Duplicate serialized value for #{name}: #{serialized.inspect} is already used"
|
|
19
21
|
end
|
|
@@ -32,6 +34,17 @@ module Enummify
|
|
|
32
34
|
alias _load deserialize
|
|
33
35
|
end
|
|
34
36
|
|
|
37
|
+
# Build a set of this enum's members, backed by a bitmask.
|
|
38
|
+
# The attached class cannot appear in a parameter, so the member type is taken from the arguments instead.
|
|
39
|
+
# Mixing enums therefore yields a set of the wrong member type, which is rejected wherever that set is used.
|
|
40
|
+
# At least one member is required, because an empty set has nothing to take the member type from.
|
|
41
|
+
# Use EnumSet.none for that.
|
|
42
|
+
#: [M] (M & Enum, *(M & Enum)) -> EnumSet[M]
|
|
43
|
+
def self.set(member, *members)
|
|
44
|
+
# A set is unordered, so the first member goes on the end of the rest rather than paying to shift them along.
|
|
45
|
+
EnumSet.from(self, members.push(member)) #: as EnumSet[M]
|
|
46
|
+
end
|
|
47
|
+
|
|
35
48
|
# Look up a member, returning nil for an unknown string.
|
|
36
49
|
#: (String) -> instance?
|
|
37
50
|
def self.try_deserialize(serialized)
|
|
@@ -59,6 +72,15 @@ module Enummify
|
|
|
59
72
|
serialize
|
|
60
73
|
end
|
|
61
74
|
|
|
75
|
+
# The member's bit in EnumSet and EnumHash masks, which is 1 shifted left by the ordinal.
|
|
76
|
+
# It is public only so those classes can read it without a slower private lookup, and is not meant for use
|
|
77
|
+
# outside Enummify.
|
|
78
|
+
# It is computed once because shifting at every use measured slower, both when building a mask and when testing
|
|
79
|
+
# one that is an immediate Integer.
|
|
80
|
+
# Like the ordinal, it changes when members are inserted or reordered.
|
|
81
|
+
#: Integer
|
|
82
|
+
attr_reader :bit
|
|
83
|
+
|
|
62
84
|
# Enum members are immutable singletons, including copies requested as unfrozen.
|
|
63
85
|
# rubocop:disable Lint/UnusedMethodArgument
|
|
64
86
|
#: (?freeze: bool?) -> self
|
|
@@ -77,6 +99,13 @@ module Enummify
|
|
|
77
99
|
"#<#{self.class.name}: #{serialize.inspect}>"
|
|
78
100
|
end
|
|
79
101
|
|
|
102
|
+
# The member's 0-based position in declaration order, which EnumHash uses as its slot.
|
|
103
|
+
# It is public only so EnumSet and EnumHash can read it without a slower private lookup, and is not meant for use
|
|
104
|
+
# outside Enummify.
|
|
105
|
+
# It changes when members are inserted or reordered, so persist serialize rather than this.
|
|
106
|
+
#: Integer
|
|
107
|
+
attr_reader :ordinal
|
|
108
|
+
|
|
80
109
|
#: () -> String
|
|
81
110
|
def serialize
|
|
82
111
|
@serialized || raise(ArgumentError, 'Enum members must be assigned to a constant before serialization')
|
|
@@ -90,8 +119,15 @@ module Enummify
|
|
|
90
119
|
private
|
|
91
120
|
|
|
92
121
|
# The constant name is only available after construction has returned.
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
# The ordinal and bit must be assigned here rather than by the caller because this method freezes the member.
|
|
123
|
+
# Assigning conditionally leaves an already registered member untouched, so aliasing one reaches the caller's
|
|
124
|
+
# duplicate check rather than failing to write to a frozen member.
|
|
125
|
+
#: (Symbol, Integer) -> String
|
|
126
|
+
def finalize(constant, ordinal)
|
|
127
|
+
unless frozen?
|
|
128
|
+
@ordinal = ordinal
|
|
129
|
+
@bit = 1 << ordinal
|
|
130
|
+
end
|
|
95
131
|
@serialized ||= constant.name
|
|
96
132
|
freeze
|
|
97
133
|
@serialized
|
|
@@ -99,6 +135,11 @@ module Enummify
|
|
|
99
135
|
|
|
100
136
|
#: (?String?) -> void
|
|
101
137
|
def initialize(serialized = nil)
|
|
138
|
+
# Declaration order and its bit, assigned during registration.
|
|
139
|
+
# They start as Integers rather than nil so the readers need no check, because every EnumSet and EnumHash
|
|
140
|
+
# operation reads one of them.
|
|
141
|
+
@bit = 0 #: Integer
|
|
142
|
+
@ordinal = -1 #: Integer
|
|
102
143
|
@serialized = serialized&.dup&.freeze #: String?
|
|
103
144
|
end
|
|
104
145
|
end
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Enummify
|
|
5
|
+
# A mutable map keyed by members of a single enum, stored as an array indexed by declaration order.
|
|
6
|
+
#
|
|
7
|
+
# A member's ordinal is its slot, so a lookup is an array index rather than a hash of the key.
|
|
8
|
+
# Keys are held in a bitmask alongside the values, which keeps a stored nil distinct from an absent key and makes
|
|
9
|
+
# the key set free to compute.
|
|
10
|
+
#
|
|
11
|
+
# Keys are checked statically, so nothing is re-checked at runtime.
|
|
12
|
+
# A key of another enum is a type error, and a consumer that defeats the type checker overwrites an unrelated slot.
|
|
13
|
+
#
|
|
14
|
+
# Entries are yielded in declaration order, not insertion order.
|
|
15
|
+
#: [Key, Value]
|
|
16
|
+
class EnumHash
|
|
17
|
+
# The type of NO_DEFAULT, which lets fetch narrow an omitted default away from a given one.
|
|
18
|
+
class NoDefault; end
|
|
19
|
+
|
|
20
|
+
# The default for an omitted fetch default, which is distinct from every value, including nil.
|
|
21
|
+
NO_DEFAULT = NoDefault.new.freeze #: NoDefault
|
|
22
|
+
|
|
23
|
+
private_constant :NO_DEFAULT, :NoDefault
|
|
24
|
+
|
|
25
|
+
# Accept type arguments at runtime without depending on sorbet-runtime.
|
|
26
|
+
# This matches T::Generic#[], which also ignores its arguments and returns self, so a consumer can write
|
|
27
|
+
# EnumHash[Status, Integer] inside a sig.
|
|
28
|
+
#: (*untyped) -> singleton(EnumHash)
|
|
29
|
+
def self.[](*)
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Build a map from a Hash whose type is already declared, which infers both the key and the value type.
|
|
34
|
+
# Sorbet cannot solve a type parameter out of a Hash literal, so a literal written at the call site yields an
|
|
35
|
+
# untyped map and its keys go unchecked.
|
|
36
|
+
# EnumHash.of infers from a literal, so prefer it unless the Hash is already typed.
|
|
37
|
+
#: [K < Enummify::Enum, V] (Class[K] & singleton(Enummify::Enum), Hash[K, V]) -> EnumHash[K, V]
|
|
38
|
+
def self.from(enum_class, entries)
|
|
39
|
+
result = new(enum_class) #: EnumHash[K, V]
|
|
40
|
+
result.merge!(entries)
|
|
41
|
+
result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Build a map from key and value pairs, which infers both types even when they are written at the call site.
|
|
45
|
+
#: [K < Enummify::Enum, V] (Class[K] & singleton(Enummify::Enum), *[K & Enummify::Enum, V]) -> EnumHash[K, V]
|
|
46
|
+
def self.of(enum_class, *entries)
|
|
47
|
+
result = new(enum_class) #: EnumHash[K, V]
|
|
48
|
+
entries.each { |member, value| result[member] = value }
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: (Key & Enummify::Enum) -> Value?
|
|
53
|
+
def [](member)
|
|
54
|
+
@entries[member.ordinal]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The slot is written first, so a write to a frozen map raises from its frozen slots before anything changes.
|
|
58
|
+
# Writing the presence mask first would raise from the map itself, but measured 6 ns slower per overwrite in the
|
|
59
|
+
# interpreter.
|
|
60
|
+
#: (Key & Enummify::Enum, Value) -> void
|
|
61
|
+
def []=(member, value)
|
|
62
|
+
@entries[member.ordinal] = value
|
|
63
|
+
bit = member.bit
|
|
64
|
+
# Overwriting a present key changes neither the key set nor the size, so both stay cached.
|
|
65
|
+
return if @present & bit != 0
|
|
66
|
+
|
|
67
|
+
@present |= bit
|
|
68
|
+
@size += 1
|
|
69
|
+
@keys = nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Equality accepts any object, so unlike the keyed operations it checks what it was given.
|
|
73
|
+
#: (untyped) -> bool
|
|
74
|
+
def ==(other)
|
|
75
|
+
same_enum_and_keys?(other) && other.entries == @entries
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Remove every entry, keeping the capacity already allocated for this enum.
|
|
79
|
+
#: () -> self
|
|
80
|
+
def clear
|
|
81
|
+
@entries = Array.new(@entries.length)
|
|
82
|
+
@present = 0
|
|
83
|
+
@size = 0
|
|
84
|
+
@keys = nil
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Remove an entry and return the value it held, or nil when the key was absent.
|
|
89
|
+
#: (Key & Enummify::Enum) -> Value?
|
|
90
|
+
def delete(member)
|
|
91
|
+
ordinal = member.ordinal
|
|
92
|
+
return nil unless @present[ordinal] == 1
|
|
93
|
+
|
|
94
|
+
value = @entries[ordinal]
|
|
95
|
+
@entries[ordinal] = nil
|
|
96
|
+
# The key is present, so subtracting its bit clears it.
|
|
97
|
+
# Subtracting measured faster than ^ in the interpreter, because subtraction has a specialized instruction and ^
|
|
98
|
+
# is an ordinary method call.
|
|
99
|
+
@present -= member.bit
|
|
100
|
+
@size -= 1
|
|
101
|
+
@keys = nil
|
|
102
|
+
value
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Yield each present key and its value in declaration order.
|
|
106
|
+
# A while loop over the cached key array measured faster than a block inside the key array's each, because it
|
|
107
|
+
# skips a block call per entry.
|
|
108
|
+
#: () { (Key & Enummify::Enum, Value) -> void } -> self
|
|
109
|
+
def each(&)
|
|
110
|
+
members = keys.to_a
|
|
111
|
+
entries = @entries
|
|
112
|
+
index = 0
|
|
113
|
+
while index < members.length
|
|
114
|
+
member = members[index] #: as !nil
|
|
115
|
+
# Only present keys are yielded, so the slot holds a value that was written rather than an empty slot.
|
|
116
|
+
value = entries[member.ordinal] #: as Value
|
|
117
|
+
yield(member, value)
|
|
118
|
+
index += 1
|
|
119
|
+
end
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#: () -> bool
|
|
124
|
+
def empty?
|
|
125
|
+
@present.zero?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Like Hash#eql?, this compares values with eql? rather than ==, so a map holding 1 is not eql? to one holding 1.0.
|
|
129
|
+
#: (untyped) -> bool
|
|
130
|
+
def eql?(other)
|
|
131
|
+
same_enum_and_keys?(other) && other.entries.eql?(@entries)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Return the value for a member.
|
|
135
|
+
# When the key is absent, return the block's result or else the default, and raise KeyError when given neither.
|
|
136
|
+
# As in Hash#fetch, a block takes precedence over a default.
|
|
137
|
+
# Hash#fetch also warns when given both, which the exported RBI's overloads reject statically instead.
|
|
138
|
+
# The optional default measured about 5 ns slower per call in the interpreter whether it defaulted to a constant
|
|
139
|
+
# or to nil, so the cost is the optional parameter itself, and under YJIT it measured within noise.
|
|
140
|
+
#: [D] (Key & Enummify::Enum, ?(D | NoDefault)) ?{ (Key & Enummify::Enum) -> D } -> (Value | D)
|
|
141
|
+
def fetch(member, default = NO_DEFAULT, &block)
|
|
142
|
+
ordinal = member.ordinal
|
|
143
|
+
if @present[ordinal] == 1
|
|
144
|
+
# A present key was written, so the slot holds a value rather than an empty slot.
|
|
145
|
+
value = @entries[ordinal] #: as Value
|
|
146
|
+
return value
|
|
147
|
+
end
|
|
148
|
+
return block.call(member) if block
|
|
149
|
+
|
|
150
|
+
# Matching on the class rather than comparing with NO_DEFAULT narrows the default to its type parameter.
|
|
151
|
+
case default
|
|
152
|
+
when NoDefault then raise KeyError, "key not found: #{member.inspect}"
|
|
153
|
+
else default
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Freeze the map, first caching its keys and freezing its slots.
|
|
158
|
+
#: () -> self
|
|
159
|
+
def freeze
|
|
160
|
+
prepare_to_freeze
|
|
161
|
+
super
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Maps that are eql? hash alike, so a map can be a Hash key.
|
|
165
|
+
# Like a Hash, a map that changes while it is a key is no longer found under it.
|
|
166
|
+
#: () -> Integer
|
|
167
|
+
def hash
|
|
168
|
+
[@enum_class, @present, @entries].hash
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
#: () -> String
|
|
172
|
+
def inspect
|
|
173
|
+
"#<Enummify::EnumHash[#{@enum_class.name}]: #{to_h.inspect}>"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Masking by the member's bit rather than indexing by ordinal matches EnumSet#include?, for the same reason.
|
|
177
|
+
#: (Key & Enummify::Enum) -> bool
|
|
178
|
+
def key?(member)
|
|
179
|
+
@present & member.bit != 0
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Return the present keys as a set, which makes key algebra across two maps a single Integer operation.
|
|
183
|
+
#: () -> EnumSet[Key & Enummify::Enum]
|
|
184
|
+
def keys
|
|
185
|
+
cached = @keys
|
|
186
|
+
return cached if cached
|
|
187
|
+
|
|
188
|
+
built = EnumSet.new(@enum_class, @present)
|
|
189
|
+
# Marshal.load with freeze: true freezes a map without calling freeze, so such a map builds its keys every time.
|
|
190
|
+
@keys = built unless frozen?
|
|
191
|
+
built
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# A copy starts with this map's slots, size and keys, so only the other entries are stored.
|
|
195
|
+
#: (EnumHash[Key, Value] | Hash[Key, Value]) -> EnumHash[Key, Value]
|
|
196
|
+
def merge(entries)
|
|
197
|
+
dup.merge!(entries)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#: (EnumHash[Key, Value] | Hash[Key, Value]) -> self
|
|
201
|
+
def merge!(entries)
|
|
202
|
+
# Another map's slots line up with this map's, so they are copied directly rather than stored entry by entry.
|
|
203
|
+
if entries.is_a?(EnumHash)
|
|
204
|
+
overlay(entries)
|
|
205
|
+
else
|
|
206
|
+
entries.each do |member, value|
|
|
207
|
+
# A key is always an enum member, which a type member on its own does not carry into the body.
|
|
208
|
+
key = member #: as Key & Enummify::Enum
|
|
209
|
+
self[key] = value
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
self
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The number of present keys, which is counted as keys are added and removed so that reading it walks nothing.
|
|
216
|
+
#: Integer
|
|
217
|
+
attr_reader :size
|
|
218
|
+
|
|
219
|
+
#: () -> Hash[Key, Value]
|
|
220
|
+
def to_h
|
|
221
|
+
result = {} #: Hash[Key, Value]
|
|
222
|
+
each { |member, value| result[member] = value }
|
|
223
|
+
result
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
#: () -> String
|
|
227
|
+
def to_s
|
|
228
|
+
inspect
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Return the values of the present keys, in declaration order of their keys.
|
|
232
|
+
#: () -> Array[Value]
|
|
233
|
+
def values
|
|
234
|
+
result = [] #: Array[Value]
|
|
235
|
+
each { |_member, value| result << value }
|
|
236
|
+
result
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
alias each_pair each
|
|
240
|
+
alias has_key? key?
|
|
241
|
+
alias include? key?
|
|
242
|
+
alias length size
|
|
243
|
+
alias member? key?
|
|
244
|
+
alias store []=
|
|
245
|
+
|
|
246
|
+
protected
|
|
247
|
+
|
|
248
|
+
# Exposed to sibling maps so equality can compare state without widening the public API.
|
|
249
|
+
#: Array[Value?]
|
|
250
|
+
attr_reader :entries
|
|
251
|
+
|
|
252
|
+
#: singleton(Enummify::Enum)
|
|
253
|
+
attr_reader :enum_class
|
|
254
|
+
|
|
255
|
+
#: Integer
|
|
256
|
+
attr_reader :present
|
|
257
|
+
|
|
258
|
+
private
|
|
259
|
+
|
|
260
|
+
# new is public only for the reason given at EnumSet#initialize, and is not meant for use outside Enummify.
|
|
261
|
+
# The capacity is read once, which is sound because members are declared in the class body and never added later.
|
|
262
|
+
#: (singleton(Enummify::Enum)) -> void
|
|
263
|
+
def initialize(enum_class)
|
|
264
|
+
@enum_class = enum_class
|
|
265
|
+
@entries = Array.new(enum_class.values.length) #: Array[Value?]
|
|
266
|
+
@present = 0 #: Integer
|
|
267
|
+
@size = 0 #: Integer
|
|
268
|
+
@keys = nil #: EnumSet[Key & Enummify::Enum]?
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Kernel#clone freezes the copy without calling freeze, so a copy that will be frozen prepares for it here.
|
|
272
|
+
#: (EnumHash[Key, Value], ?freeze: bool?) -> void
|
|
273
|
+
def initialize_clone(source, freeze: nil)
|
|
274
|
+
super
|
|
275
|
+
prepare_to_freeze if freeze.nil? ? source.frozen? : freeze
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# A copy gets its own slots, so writing to it leaves the source alone.
|
|
279
|
+
#: (EnumHash[Key, Value]) -> void
|
|
280
|
+
def initialize_copy(source)
|
|
281
|
+
super
|
|
282
|
+
@entries = @entries.dup
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Copy another map's present slots over this map's, then take the union of both key sets.
|
|
286
|
+
# A while loop over the other map's cached key array measured faster than storing its entries one by one.
|
|
287
|
+
# Counting the added keys while copying replaced counting the bits of a newly built key set.
|
|
288
|
+
# It measured about a quarter faster for 8 members, and for larger enums a tenth faster under YJIT but up to 5%
|
|
289
|
+
# slower in the interpreter.
|
|
290
|
+
# The slots are written first, so a frozen map raises from its frozen slots before its keys change.
|
|
291
|
+
#: (EnumHash[Key, Value]) -> void
|
|
292
|
+
def overlay(other)
|
|
293
|
+
entries = @entries
|
|
294
|
+
other_entries = other.entries
|
|
295
|
+
present = @present
|
|
296
|
+
size = @size
|
|
297
|
+
members = other.keys.to_a
|
|
298
|
+
index = 0
|
|
299
|
+
while index < members.length
|
|
300
|
+
member = members[index] #: as !nil
|
|
301
|
+
ordinal = member.ordinal
|
|
302
|
+
entries[ordinal] = other_entries[ordinal]
|
|
303
|
+
size += 1 unless present[ordinal] == 1
|
|
304
|
+
index += 1
|
|
305
|
+
end
|
|
306
|
+
return if size == @size
|
|
307
|
+
|
|
308
|
+
@present = present | other.present
|
|
309
|
+
@size = size
|
|
310
|
+
@keys = nil
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Cache the keys and freeze the slots, which a frozen map could no longer do.
|
|
314
|
+
# A write stores its value in a slot before it changes the map itself, so frozen slots are what make a write to a
|
|
315
|
+
# frozen map raise before anything changes.
|
|
316
|
+
#: () -> void
|
|
317
|
+
def prepare_to_freeze
|
|
318
|
+
keys
|
|
319
|
+
@entries.freeze
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Check that another object is a map over the same enum with the same keys, which both == and eql? require.
|
|
323
|
+
#: (untyped) -> bool
|
|
324
|
+
def same_enum_and_keys?(other)
|
|
325
|
+
return false unless other.is_a?(EnumHash)
|
|
326
|
+
|
|
327
|
+
other.enum_class.equal?(@enum_class) && other.present == @present
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Enummify
|
|
5
|
+
# An immutable set of members of a single enum, stored as one Integer bitmask.
|
|
6
|
+
#
|
|
7
|
+
# Each member occupies the bit at its declaration index, so union, intersection, difference and subset tests are
|
|
8
|
+
# single Integer operations rather than hash walks.
|
|
9
|
+
# A mask for an enum with 62 or fewer members is an immediate value, so such a set holds no heap-allocated storage.
|
|
10
|
+
#
|
|
11
|
+
# Operations take a set of the same enum and are checked statically, so nothing is re-checked at runtime.
|
|
12
|
+
# Two enums number their members independently, so combining sets of different enums is a type error, and a
|
|
13
|
+
# consumer that defeats the type checker gets a meaningless result rather than an exception.
|
|
14
|
+
#
|
|
15
|
+
# Sets are immutable: every operation returns a new set.
|
|
16
|
+
# They are not frozen, because the member array and size are computed on first use and cached.
|
|
17
|
+
# Materializing eagerly instead would cost far more than the bitmask saves.
|
|
18
|
+
# Freezing a set caches both first, so a frozen set reads them as quickly as one that is not frozen.
|
|
19
|
+
#
|
|
20
|
+
# The type parameter is named Elem so that including Enumerable supplies map, select and friends with the member
|
|
21
|
+
# type already bound.
|
|
22
|
+
#: [Elem]
|
|
23
|
+
class EnumSet
|
|
24
|
+
include Enumerable
|
|
25
|
+
|
|
26
|
+
# Accept type arguments at runtime without depending on sorbet-runtime.
|
|
27
|
+
# This matches T::Generic#[], which also ignores its arguments and returns self, so a consumer can write
|
|
28
|
+
# EnumSet[Status] inside a sig.
|
|
29
|
+
#: (*untyped) -> singleton(EnumSet)
|
|
30
|
+
def self.[](*)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Build the set of every member of the enum.
|
|
35
|
+
#: [M < Enummify::Enum] (Class[M] & singleton(Enummify::Enum)) -> EnumSet[M]
|
|
36
|
+
def self.all(enum_class)
|
|
37
|
+
new(enum_class, (1 << enum_class.values.length) - 1)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Build a set from a collection of members, for callers that already have one.
|
|
41
|
+
# Enumerable is covariant in its element, so an Array of a specific member type is accepted here.
|
|
42
|
+
#: [M < Enummify::Enum] (Class[M] & singleton(Enummify::Enum), Enumerable[M & Enummify::Enum]) -> EnumSet[M]
|
|
43
|
+
def self.from(enum_class, members)
|
|
44
|
+
new(enum_class, mask_for(members))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Build the empty set for the enum.
|
|
48
|
+
#: [M < Enummify::Enum] (Class[M] & singleton(Enummify::Enum)) -> EnumSet[M]
|
|
49
|
+
def self.none(enum_class)
|
|
50
|
+
new(enum_class, 0)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Build a set from the given members.
|
|
54
|
+
#: [M < Enummify::Enum] (Class[M] & singleton(Enummify::Enum), *(M & Enummify::Enum)) -> EnumSet[M]
|
|
55
|
+
def self.of(enum_class, *members)
|
|
56
|
+
new(enum_class, mask_for(members))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Fold members into a bitmask.
|
|
60
|
+
#: (Enumerable[Enummify::Enum]) -> Integer
|
|
61
|
+
def self.mask_for(members)
|
|
62
|
+
mask = 0
|
|
63
|
+
members.each do |member|
|
|
64
|
+
mask |= member.bit
|
|
65
|
+
end
|
|
66
|
+
mask
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private_class_method :mask_for
|
|
70
|
+
|
|
71
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
72
|
+
def &(other)
|
|
73
|
+
derive(@mask & other.mask)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
77
|
+
def +(other)
|
|
78
|
+
derive(@mask | other.mask)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
82
|
+
def -(other)
|
|
83
|
+
derive(@mask & ~other.mask)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Equality accepts any object, so unlike the set operations it checks what it was given.
|
|
87
|
+
#: (untyped) -> bool
|
|
88
|
+
def ==(other)
|
|
89
|
+
return false unless other.is_a?(EnumSet)
|
|
90
|
+
|
|
91
|
+
other.enum_class.equal?(@enum_class) && other.mask == @mask
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Support `case member when SOME_SET`.
|
|
95
|
+
#: (untyped) -> bool
|
|
96
|
+
def ===(member)
|
|
97
|
+
include?(member)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
101
|
+
def ^(other)
|
|
102
|
+
derive(@mask ^ other.mask)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
106
|
+
def |(other)
|
|
107
|
+
derive(@mask | other.mask)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Return the set of this enum's members that this set does not contain.
|
|
111
|
+
#: () -> EnumSet[Elem]
|
|
112
|
+
def ~
|
|
113
|
+
derive(((1 << @enum_class.values.length) - 1) ^ @mask)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Return a set that also contains the member.
|
|
117
|
+
#: (Elem & Enummify::Enum) -> EnumSet[Elem]
|
|
118
|
+
def add(member)
|
|
119
|
+
derive(@mask | member.bit)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Return a set without the member.
|
|
123
|
+
#: (Elem & Enummify::Enum) -> EnumSet[Elem]
|
|
124
|
+
def delete(member)
|
|
125
|
+
derive(@mask & ~member.bit)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
129
|
+
def difference(other)
|
|
130
|
+
derive(@mask & ~other.mask)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#: (EnumSet[Elem]) -> bool
|
|
134
|
+
def disjoint?(other)
|
|
135
|
+
(@mask & other.mask).zero?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Reading the cached members directly rather than through to_a measured faster in the interpreter, because it
|
|
139
|
+
# skips two method calls.
|
|
140
|
+
# @override
|
|
141
|
+
#: () { (Elem) -> void } -> self
|
|
142
|
+
def each(&)
|
|
143
|
+
cached = @members || members #: as Array[Elem]
|
|
144
|
+
cached.each(&)
|
|
145
|
+
self
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#: () -> bool
|
|
149
|
+
def empty?
|
|
150
|
+
@mask.zero?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#: (untyped) -> bool
|
|
154
|
+
def eql?(other)
|
|
155
|
+
self == other
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Freeze the set, first caching its members and size.
|
|
159
|
+
#: () -> self
|
|
160
|
+
def freeze
|
|
161
|
+
members
|
|
162
|
+
size
|
|
163
|
+
super
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#: () -> Integer
|
|
167
|
+
def hash
|
|
168
|
+
[@enum_class, @mask].hash
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Masking by the member's bit measured faster than Set#include? for every enum of 62 or fewer members, whose masks
|
|
172
|
+
# are immediate Integers, and indexing the mask by ordinal did not in the interpreter.
|
|
173
|
+
# Enumerable#include? takes any member type, so narrowing the parameter is declared incompatible rather than cast
|
|
174
|
+
# inside, because the local that a cast needs measured 7 ns slower in the interpreter.
|
|
175
|
+
# Enums of more than 62 members are not a performance target, so masking is kept even though masking their Bignum
|
|
176
|
+
# masks allocates, which indexing by ordinal would not.
|
|
177
|
+
# @override(allow_incompatible: true)
|
|
178
|
+
#: (Elem & Enummify::Enum) -> bool
|
|
179
|
+
def include?(member)
|
|
180
|
+
@mask & member.bit != 0
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
#: () -> String
|
|
184
|
+
def inspect
|
|
185
|
+
"#<Enummify::EnumSet[#{@enum_class.name}]: #{members.map(&:serialize).inspect}>"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
#: (EnumSet[Elem]) -> bool
|
|
189
|
+
def intersect?(other)
|
|
190
|
+
!(@mask & other.mask).zero?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
194
|
+
def intersection(other)
|
|
195
|
+
derive(@mask & other.mask)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Mapping the cached members directly measured faster than Enumerable#map, which yields every member through each.
|
|
199
|
+
# Called without a block, Array#map returns an Enumerator just as Enumerable#map does.
|
|
200
|
+
# Sorbet allows overloads only in RBI files, so the blockless form is typed by Enumerable#map in the exported RBI.
|
|
201
|
+
# @override
|
|
202
|
+
#: [U] () { (Elem) -> U } -> Array[U]
|
|
203
|
+
def map(&)
|
|
204
|
+
cached = @members || members #: as Array[Elem]
|
|
205
|
+
cached.map(&)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
#: () -> Integer
|
|
209
|
+
def size
|
|
210
|
+
@size || count_members
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
#: (EnumSet[Elem]) -> bool
|
|
214
|
+
def subset?(other)
|
|
215
|
+
@mask & other.mask == @mask
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
#: (EnumSet[Elem]) -> bool
|
|
219
|
+
def superset?(other)
|
|
220
|
+
mask = other.mask
|
|
221
|
+
@mask & mask == mask
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# @override
|
|
225
|
+
#: () -> Array[Elem]
|
|
226
|
+
def to_a
|
|
227
|
+
members #: as Array[Elem]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
#: () -> String
|
|
231
|
+
def to_s
|
|
232
|
+
inspect
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
#: (EnumSet[Elem]) -> EnumSet[Elem]
|
|
236
|
+
def union(other)
|
|
237
|
+
derive(@mask | other.mask)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
alias collect map
|
|
241
|
+
alias complement ~
|
|
242
|
+
alias length size
|
|
243
|
+
alias member? include?
|
|
244
|
+
|
|
245
|
+
protected
|
|
246
|
+
|
|
247
|
+
# Exposed to sibling sets so operations can read the other side without widening the public API.
|
|
248
|
+
#: singleton(Enummify::Enum)
|
|
249
|
+
attr_reader :enum_class
|
|
250
|
+
|
|
251
|
+
#: Integer
|
|
252
|
+
attr_reader :mask
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
# Count the members and cache the count.
|
|
257
|
+
# Ruby has no popcount. Counting "1" in the binary representation measured fastest for small and large masks.
|
|
258
|
+
#: () -> Integer
|
|
259
|
+
def count_members
|
|
260
|
+
counted = @mask.to_s(2).count('1')
|
|
261
|
+
# Kernel#clone and Marshal.load with freeze: true freeze a set without calling freeze, so such a set counts every
|
|
262
|
+
# time.
|
|
263
|
+
return counted if frozen?
|
|
264
|
+
|
|
265
|
+
@size = counted
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Build a sibling set over the same enum.
|
|
269
|
+
#: (Integer) -> EnumSet[Elem]
|
|
270
|
+
def derive(mask)
|
|
271
|
+
EnumSet.new(@enum_class, mask)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# new is public only because hiding it with private_class_method adds a visibility override that Ruby 4.0's
|
|
275
|
+
# opt_new instruction does not recognize.
|
|
276
|
+
# Every new set then took the slow path, which measured about twice as slow in the interpreter and three times as
|
|
277
|
+
# slow under YJIT.
|
|
278
|
+
# It is not meant for use outside Enummify, and the exported RBI declares no initialize, so Sorbet rejects a
|
|
279
|
+
# consumer's call to new that passes it an enum and a mask.
|
|
280
|
+
#: (singleton(Enummify::Enum), Integer) -> void
|
|
281
|
+
def initialize(enum_class, mask)
|
|
282
|
+
@enum_class = enum_class
|
|
283
|
+
@mask = mask
|
|
284
|
+
@members = nil #: Array[Enummify::Enum]?
|
|
285
|
+
@size = nil #: Integer?
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Materialize the members once and cache them.
|
|
289
|
+
# Walking the mask is slower than iterating an Array, so every iteration after the first reads the cache.
|
|
290
|
+
# Extracting the lowest set bit costs one step per present member and yields declaration order.
|
|
291
|
+
# Negating as 0 - remaining and clearing by subtraction measured faster than -remaining and ^, because binary
|
|
292
|
+
# minus has a specialized instruction while unary minus and ^ are ordinary method calls.
|
|
293
|
+
#: () -> Array[Enummify::Enum]
|
|
294
|
+
def members
|
|
295
|
+
cached = @members
|
|
296
|
+
return cached if cached
|
|
297
|
+
|
|
298
|
+
materialized = []
|
|
299
|
+
values = @enum_class.values
|
|
300
|
+
remaining = @mask
|
|
301
|
+
while remaining != 0
|
|
302
|
+
lowest = remaining & (0 - remaining)
|
|
303
|
+
materialized << values[lowest.bit_length - 1]
|
|
304
|
+
remaining -= lowest
|
|
305
|
+
end
|
|
306
|
+
materialized.freeze
|
|
307
|
+
# Kernel#clone and Marshal.load with freeze: true freeze a set without calling freeze, so such a set materializes
|
|
308
|
+
# every time.
|
|
309
|
+
return materialized if frozen?
|
|
310
|
+
|
|
311
|
+
@members = materialized
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
data/lib/enummify/version.rb
CHANGED
data/lib/enummify.rb
CHANGED
data/rbi/enummify.rbi
CHANGED
|
@@ -13,6 +13,16 @@ module Enummify
|
|
|
13
13
|
alias _load deserialize
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
sig do
|
|
17
|
+
type_parameters(:M)
|
|
18
|
+
.params(
|
|
19
|
+
member: T.all(T.type_parameter(:M), Enummify::Enum),
|
|
20
|
+
members: T.all(T.type_parameter(:M), Enummify::Enum)
|
|
21
|
+
)
|
|
22
|
+
.returns(EnumSet[T.type_parameter(:M)])
|
|
23
|
+
end
|
|
24
|
+
def self.set(member, *members); end
|
|
25
|
+
|
|
16
26
|
sig { params(serialized: String).returns(T.nilable(T.attached_class)) }
|
|
17
27
|
def self.try_deserialize(serialized); end
|
|
18
28
|
|
|
@@ -22,6 +32,11 @@ module Enummify
|
|
|
22
32
|
sig { params(_depth: Integer).returns(String) }
|
|
23
33
|
def _dump(_depth); end
|
|
24
34
|
|
|
35
|
+
# The member's bit in EnumSet and EnumHash masks.
|
|
36
|
+
# It is public only so those classes can read it quickly, and is not meant for use outside Enummify.
|
|
37
|
+
sig { returns(Integer) }
|
|
38
|
+
def bit; end
|
|
39
|
+
|
|
25
40
|
sig { params(freeze: T.nilable(T::Boolean)).returns(T.self_type) }
|
|
26
41
|
def clone(freeze: true); end
|
|
27
42
|
|
|
@@ -31,6 +46,11 @@ module Enummify
|
|
|
31
46
|
sig { returns(String) }
|
|
32
47
|
def inspect; end
|
|
33
48
|
|
|
49
|
+
# The member's 0-based position in declaration order.
|
|
50
|
+
# It is public only so EnumSet and EnumHash can read it quickly, and is not meant for use outside Enummify.
|
|
51
|
+
sig { returns(Integer) }
|
|
52
|
+
def ordinal; end
|
|
53
|
+
|
|
34
54
|
sig { returns(String) }
|
|
35
55
|
def serialize; end
|
|
36
56
|
|
|
@@ -42,4 +62,240 @@ module Enummify
|
|
|
42
62
|
sig { params(serialized: T.nilable(String)).void }
|
|
43
63
|
def initialize(serialized = nil); end
|
|
44
64
|
end
|
|
65
|
+
|
|
66
|
+
# A mutable map keyed by members of a single enum, stored as an array indexed by declaration order.
|
|
67
|
+
# new is public at runtime only for speed, and initialize is deliberately not declared here, so Sorbet rejects a
|
|
68
|
+
# call to new with arguments.
|
|
69
|
+
class EnumHash
|
|
70
|
+
extend T::Generic
|
|
71
|
+
extend T::Sig
|
|
72
|
+
|
|
73
|
+
Key = type_member
|
|
74
|
+
Value = type_member
|
|
75
|
+
|
|
76
|
+
sig do
|
|
77
|
+
type_parameters(:K, :V)
|
|
78
|
+
.params(
|
|
79
|
+
enum_class: T.all(T::Class[T.type_parameter(:K)], T.class_of(Enummify::Enum)),
|
|
80
|
+
entries: T::Hash[T.type_parameter(:K), T.type_parameter(:V)]
|
|
81
|
+
)
|
|
82
|
+
.returns(EnumHash[T.type_parameter(:K), T.type_parameter(:V)])
|
|
83
|
+
end
|
|
84
|
+
def self.from(enum_class, entries); end
|
|
85
|
+
|
|
86
|
+
sig do
|
|
87
|
+
type_parameters(:K, :V)
|
|
88
|
+
.params(
|
|
89
|
+
enum_class: T.all(T::Class[T.type_parameter(:K)], T.class_of(Enummify::Enum)),
|
|
90
|
+
entries: [T.all(T.type_parameter(:K), Enummify::Enum), T.type_parameter(:V)]
|
|
91
|
+
)
|
|
92
|
+
.returns(EnumHash[T.type_parameter(:K), T.type_parameter(:V)])
|
|
93
|
+
end
|
|
94
|
+
def self.of(enum_class, *entries); end
|
|
95
|
+
|
|
96
|
+
sig { params(member: T.all(Key, Enummify::Enum)).returns(T.nilable(Value)) }
|
|
97
|
+
def [](member); end
|
|
98
|
+
|
|
99
|
+
sig { params(member: T.all(Key, Enummify::Enum), value: Value).void }
|
|
100
|
+
def []=(member, value); end
|
|
101
|
+
|
|
102
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
103
|
+
def ==(other); end
|
|
104
|
+
|
|
105
|
+
sig { returns(T.self_type) }
|
|
106
|
+
def clear; end
|
|
107
|
+
|
|
108
|
+
sig { params(member: T.all(Key, Enummify::Enum)).returns(T.nilable(Value)) }
|
|
109
|
+
def delete(member); end
|
|
110
|
+
|
|
111
|
+
sig { params(block: T.proc.params(member: T.all(Key, Enummify::Enum), value: Value).void).returns(T.self_type) }
|
|
112
|
+
def each(&block); end
|
|
113
|
+
|
|
114
|
+
sig { returns(T::Boolean) }
|
|
115
|
+
def empty?; end
|
|
116
|
+
|
|
117
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
118
|
+
def eql?(other); end
|
|
119
|
+
|
|
120
|
+
# The overloads mirror Hash#fetch, and accept a default or a block but not both.
|
|
121
|
+
sig { params(member: T.all(Key, Enummify::Enum)).returns(Value) }
|
|
122
|
+
sig do
|
|
123
|
+
type_parameters(:D)
|
|
124
|
+
.params(member: T.all(Key, Enummify::Enum), default: T.type_parameter(:D))
|
|
125
|
+
.returns(T.any(Value, T.type_parameter(:D)))
|
|
126
|
+
end
|
|
127
|
+
sig do
|
|
128
|
+
type_parameters(:D)
|
|
129
|
+
.params(
|
|
130
|
+
member: T.all(Key, Enummify::Enum),
|
|
131
|
+
block: T.proc.params(member: T.all(Key, Enummify::Enum)).returns(T.type_parameter(:D))
|
|
132
|
+
)
|
|
133
|
+
.returns(T.any(Value, T.type_parameter(:D)))
|
|
134
|
+
end
|
|
135
|
+
def fetch(member, default = T.unsafe(nil), &block); end
|
|
136
|
+
|
|
137
|
+
sig { returns(Integer) }
|
|
138
|
+
def hash; end
|
|
139
|
+
|
|
140
|
+
sig { returns(String) }
|
|
141
|
+
def inspect; end
|
|
142
|
+
|
|
143
|
+
sig { params(member: T.all(Key, Enummify::Enum)).returns(T::Boolean) }
|
|
144
|
+
def key?(member); end
|
|
145
|
+
|
|
146
|
+
sig { returns(EnumSet[T.all(Key, Enummify::Enum)]) }
|
|
147
|
+
def keys; end
|
|
148
|
+
|
|
149
|
+
sig { params(entries: T.any(EnumHash[Key, Value], T::Hash[Key, Value])).returns(EnumHash[Key, Value]) }
|
|
150
|
+
def merge(entries); end
|
|
151
|
+
|
|
152
|
+
sig { params(entries: T.any(EnumHash[Key, Value], T::Hash[Key, Value])).returns(T.self_type) }
|
|
153
|
+
def merge!(entries); end
|
|
154
|
+
|
|
155
|
+
sig { returns(Integer) }
|
|
156
|
+
def size; end
|
|
157
|
+
|
|
158
|
+
sig { returns(T::Hash[Key, Value]) }
|
|
159
|
+
def to_h; end
|
|
160
|
+
|
|
161
|
+
sig { returns(String) }
|
|
162
|
+
def to_s; end
|
|
163
|
+
|
|
164
|
+
sig { returns(T::Array[Value]) }
|
|
165
|
+
def values; end
|
|
166
|
+
|
|
167
|
+
alias each_pair each
|
|
168
|
+
alias has_key? key?
|
|
169
|
+
alias include? key?
|
|
170
|
+
alias length size
|
|
171
|
+
alias member? key?
|
|
172
|
+
alias store []=
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# An immutable set of members of a single enum, stored as one Integer bitmask.
|
|
176
|
+
# new is public at runtime only for speed, and initialize is deliberately not declared here, so Sorbet rejects a
|
|
177
|
+
# call to new with arguments.
|
|
178
|
+
class EnumSet
|
|
179
|
+
extend T::Generic
|
|
180
|
+
extend T::Sig
|
|
181
|
+
include Enumerable
|
|
182
|
+
|
|
183
|
+
Elem = type_member
|
|
184
|
+
|
|
185
|
+
sig do
|
|
186
|
+
type_parameters(:M)
|
|
187
|
+
.params(enum_class: T.all(T::Class[T.type_parameter(:M)], T.class_of(Enummify::Enum)))
|
|
188
|
+
.returns(EnumSet[T.type_parameter(:M)])
|
|
189
|
+
end
|
|
190
|
+
def self.all(enum_class); end
|
|
191
|
+
|
|
192
|
+
sig do
|
|
193
|
+
type_parameters(:M)
|
|
194
|
+
.params(
|
|
195
|
+
enum_class: T.all(T::Class[T.type_parameter(:M)], T.class_of(Enummify::Enum)),
|
|
196
|
+
members: T::Enumerable[T.all(T.type_parameter(:M), Enummify::Enum)]
|
|
197
|
+
)
|
|
198
|
+
.returns(EnumSet[T.type_parameter(:M)])
|
|
199
|
+
end
|
|
200
|
+
def self.from(enum_class, members); end
|
|
201
|
+
|
|
202
|
+
sig do
|
|
203
|
+
type_parameters(:M)
|
|
204
|
+
.params(enum_class: T.all(T::Class[T.type_parameter(:M)], T.class_of(Enummify::Enum)))
|
|
205
|
+
.returns(EnumSet[T.type_parameter(:M)])
|
|
206
|
+
end
|
|
207
|
+
def self.none(enum_class); end
|
|
208
|
+
|
|
209
|
+
sig do
|
|
210
|
+
type_parameters(:M)
|
|
211
|
+
.params(
|
|
212
|
+
enum_class: T.all(T::Class[T.type_parameter(:M)], T.class_of(Enummify::Enum)),
|
|
213
|
+
members: T.all(T.type_parameter(:M), Enummify::Enum)
|
|
214
|
+
)
|
|
215
|
+
.returns(EnumSet[T.type_parameter(:M)])
|
|
216
|
+
end
|
|
217
|
+
def self.of(enum_class, *members); end
|
|
218
|
+
|
|
219
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
220
|
+
def &(other); end
|
|
221
|
+
|
|
222
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
223
|
+
def +(other); end
|
|
224
|
+
|
|
225
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
226
|
+
def -(other); end
|
|
227
|
+
|
|
228
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
229
|
+
def ==(other); end
|
|
230
|
+
|
|
231
|
+
sig { params(member: T.untyped).returns(T::Boolean) }
|
|
232
|
+
def ===(member); end
|
|
233
|
+
|
|
234
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
235
|
+
def ^(other); end
|
|
236
|
+
|
|
237
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
238
|
+
def |(other); end
|
|
239
|
+
|
|
240
|
+
sig { returns(EnumSet[Elem]) }
|
|
241
|
+
def ~; end
|
|
242
|
+
|
|
243
|
+
sig { params(member: T.all(Elem, Enummify::Enum)).returns(EnumSet[Elem]) }
|
|
244
|
+
def add(member); end
|
|
245
|
+
|
|
246
|
+
sig { params(member: T.all(Elem, Enummify::Enum)).returns(EnumSet[Elem]) }
|
|
247
|
+
def delete(member); end
|
|
248
|
+
|
|
249
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
250
|
+
def difference(other); end
|
|
251
|
+
|
|
252
|
+
sig { params(other: EnumSet[Elem]).returns(T::Boolean) }
|
|
253
|
+
def disjoint?(other); end
|
|
254
|
+
|
|
255
|
+
sig { params(block: T.proc.params(member: Elem).void).returns(T.self_type) }
|
|
256
|
+
def each(&block); end
|
|
257
|
+
|
|
258
|
+
sig { returns(T::Boolean) }
|
|
259
|
+
def empty?; end
|
|
260
|
+
|
|
261
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
262
|
+
def eql?(other); end
|
|
263
|
+
|
|
264
|
+
sig { returns(Integer) }
|
|
265
|
+
def hash; end
|
|
266
|
+
|
|
267
|
+
sig { params(member: Elem).returns(T::Boolean) }
|
|
268
|
+
def include?(member); end
|
|
269
|
+
|
|
270
|
+
sig { returns(String) }
|
|
271
|
+
def inspect; end
|
|
272
|
+
|
|
273
|
+
sig { params(other: EnumSet[Elem]).returns(T::Boolean) }
|
|
274
|
+
def intersect?(other); end
|
|
275
|
+
|
|
276
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
277
|
+
def intersection(other); end
|
|
278
|
+
|
|
279
|
+
sig { returns(Integer) }
|
|
280
|
+
def size; end
|
|
281
|
+
|
|
282
|
+
sig { params(other: EnumSet[Elem]).returns(T::Boolean) }
|
|
283
|
+
def subset?(other); end
|
|
284
|
+
|
|
285
|
+
sig { params(other: EnumSet[Elem]).returns(T::Boolean) }
|
|
286
|
+
def superset?(other); end
|
|
287
|
+
|
|
288
|
+
sig { returns(T::Array[Elem]) }
|
|
289
|
+
def to_a; end
|
|
290
|
+
|
|
291
|
+
sig { returns(String) }
|
|
292
|
+
def to_s; end
|
|
293
|
+
|
|
294
|
+
sig { params(other: EnumSet[Elem]).returns(EnumSet[Elem]) }
|
|
295
|
+
def union(other); end
|
|
296
|
+
|
|
297
|
+
alias complement ~
|
|
298
|
+
alias length size
|
|
299
|
+
alias member? include?
|
|
300
|
+
end
|
|
45
301
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: enummify
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin D. Harris
|
|
@@ -58,6 +58,8 @@ extra_rdoc_files: []
|
|
|
58
58
|
files:
|
|
59
59
|
- lib/enummify.rb
|
|
60
60
|
- lib/enummify/enum.rb
|
|
61
|
+
- lib/enummify/enum_hash.rb
|
|
62
|
+
- lib/enummify/enum_set.rb
|
|
61
63
|
- lib/enummify/version.rb
|
|
62
64
|
- rbi/enummify.rbi
|
|
63
65
|
homepage: https://github.com/juharris/enummify
|
|
@@ -80,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
80
82
|
- !ruby/object:Gem::Version
|
|
81
83
|
version: '0'
|
|
82
84
|
requirements: []
|
|
83
|
-
rubygems_version: 4.0.
|
|
85
|
+
rubygems_version: 4.0.20
|
|
84
86
|
specification_version: 4
|
|
85
87
|
summary: Typed Ruby enums using RBS.
|
|
86
88
|
test_files: []
|