Class: T::Enum

Inherits:
Object
  • Object
show all
Defined in:
lib/sorbet/eraser/t/enum.rb

Overview

This is mostly copy-pasted from sorbet-runtime since we have to maintain the same behavior.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(serialized_val = nil) ⇒ Enum

Private implementation ##

[View source]

146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/sorbet/eraser/t/enum.rb', line 146

def initialize(serialized_val=nil)
  raise 'T::Enum is abstract' if self.class == T::Enum
  if !self.class.started_initializing?
    raise "Must instantiate all enum values of #{self.class} inside 'enums do'."
  end
  if self.class.fully_initialized?
    raise "Cannot instantiate a new enum value of #{self.class} after it has been initialized."
  end

  serialized_val = serialized_val.frozen? ? serialized_val : serialized_val.dup.freeze
  @serialized_val = serialized_val
  @const_name = nil
  self.class._register_instance(self)
end

Class Method Details

._load(args) ⇒ Object

[View source]

248
249
250
# File 'lib/sorbet/eraser/t/enum.rb', line 248

def self._load(args)
  deserialize(Marshal.load(args))
end

._register_instance(instance) ⇒ Object

Maintains the order in which values are defined

[View source]

190
191
192
193
# File 'lib/sorbet/eraser/t/enum.rb', line 190

def self._register_instance(instance)
  @values ||= []
  @values << instance
end

.deserialize(mongo_value) ⇒ Object

Note: Failed CriticalMethodsNoRuntimeTypingTest

[View source]

71
72
73
74
75
76
# File 'lib/sorbet/eraser/t/enum.rb', line 71

def self.deserialize(mongo_value)
  if self == T::Enum
    raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
  end
  self.from_serialized(mongo_value)
end

.each_value(&blk) ⇒ Object

This exists for compatibility with the interface of ‘Hash` & mostly to support the HashEachMethods Rubocop.

[View source]

19
20
21
22
23
24
25
# File 'lib/sorbet/eraser/t/enum.rb', line 19

def self.each_value(&blk)
  if blk
    values.each(&blk)
  else
    values.each
  end
end

.enums(&blk) ⇒ Object

Entrypoint for allowing people to register new enum values. All enum values must be defined within this block.

[View source]

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/sorbet/eraser/t/enum.rb', line 197

def self.enums(&blk)
  raise "enums cannot be defined for T::Enum" if self == T::Enum
  raise "Enum #{self} was already initialized" if @fully_initialized
  raise "Enum #{self} is still initializing" if @started_initializing

  @started_initializing = true

  @values = nil

  yield

  @mapping = nil
  @mapping = {}

  # Freeze the Enum class and bind the constant names into each of the instances.
  self.constants(false).each do |const_name|
    instance = self.const_get(const_name, false)
    if !instance.is_a?(self)
      raise "Invalid constant #{self}::#{const_name} on enum. " \
        "All constants defined for an enum must be instances itself (e.g. `Foo = new`)."
    end

    instance._bind_name(const_name)
    serialized = instance.serialize
    if @mapping.include?(serialized)
      raise "Enum values must have unique serializations. Value '#{serialized}' is repeated on #{self}."
    end
    @mapping[serialized] = instance
  end
  @values.freeze
  @mapping.freeze

  orphaned_instances = @values - @mapping.values
  if !orphaned_instances.empty?
    raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
  end

  @fully_initialized = true
end

.from_serialized(serialized_val) ⇒ self

Convert from serialized value to enum instance.

Returns:

  • (self)

Raises:

  • (KeyError)

    if serialized value does not match any instance.

[View source]

40
41
42
43
44
45
46
# File 'lib/sorbet/eraser/t/enum.rb', line 40

def self.from_serialized(serialized_val)
  res = try_deserialize(serialized_val)
  if res.nil?
    raise KeyError.new("Enum #{self} key not found: #{serialized_val.inspect}")
  end
  res
end

.fully_initialized?Boolean

Returns:

  • (Boolean)
[View source]

185
186
187
# File 'lib/sorbet/eraser/t/enum.rb', line 185

def self.fully_initialized?
  @fully_initialized ||= false
end

.has_serialized?(serialized_val) ⇒ Boolean

Note: It would have been nice to make this method final before people started overriding it.

Returns:

  • (Boolean)

    Does the given serialized value correspond with any of this enum’s values.

[View source]

50
51
52
53
54
55
56
# File 'lib/sorbet/eraser/t/enum.rb', line 50

def self.has_serialized?(serialized_val)
  if @mapping.nil?
    raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
      " Enums are not initialized until the 'enums do' block they are defined in has finished running."
  end
  @mapping.include?(serialized_val)
end

.inherited(child_class) ⇒ Object

[View source]

237
238
239
240
241
# File 'lib/sorbet/eraser/t/enum.rb', line 237

def self.inherited(child_class)
  super

  raise "Inheriting from children of T::Enum is prohibited" if self != T::Enum
end

.serialize(instance) ⇒ Object

[View source]

58
59
60
61
62
63
64
65
66
67
68
# File 'lib/sorbet/eraser/t/enum.rb', line 58

def self.serialize(instance)
  return nil if instance.nil?

  if self == T::Enum
    raise "Cannot call T::Enum.serialize directly. You must call on a specific child class."
  end
  if instance.class != self
    raise "Cannot call #serialize on a value that is not an instance of #{self}."
  end
  instance.serialize
end

.started_initializing?Boolean

Returns:

  • (Boolean)
[View source]

181
182
183
# File 'lib/sorbet/eraser/t/enum.rb', line 181

def self.started_initializing?
  @started_initializing ||= false
end

.try_deserialize(serialized_val) ⇒ Object

Convert from serialized value to enum instance

[View source]

28
29
30
31
32
33
34
# File 'lib/sorbet/eraser/t/enum.rb', line 28

def self.try_deserialize(serialized_val)
  if @mapping.nil?
    raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
      " Enums are not initialized until the 'enums do' block they are defined in has finished running."
  end
  @mapping[serialized_val]
end

.valuesObject

Enum class methods ##

[View source]

9
10
11
12
13
14
15
# File 'lib/sorbet/eraser/t/enum.rb', line 9

def self.values
  if @values.nil?
    raise "Attempting to access values of #{self.class} before it has been initialized." \
      " Enums are not initialized until the 'enums do' block they are defined in has finished running."
  end
  @values
end

Instance Method Details

#<=>(other) ⇒ Object

[View source]

105
106
107
108
109
110
111
112
# File 'lib/sorbet/eraser/t/enum.rb', line 105

def <=>(other)
  case other
  when self.class
    self.serialize <=> other.serialize
  else
    nil
  end
end

#==(other) ⇒ Object

[View source]

126
127
128
129
130
131
132
133
# File 'lib/sorbet/eraser/t/enum.rb', line 126

def ==(other)
  case other
  when String
    false
  else
    super(other)
  end
end

#===(other) ⇒ Object

[View source]

135
136
137
138
139
140
141
142
# File 'lib/sorbet/eraser/t/enum.rb', line 135

def ===(other)
  case other
  when String
    false
  else
    super(other)
  end
end

#_bind_name(const_name) ⇒ Object

[View source]

168
169
170
171
172
# File 'lib/sorbet/eraser/t/enum.rb', line 168

def _bind_name(const_name)
  @const_name = const_name
  @serialized_val = const_to_serialized_val(const_name) if @serialized_val.nil?
  freeze
end

#_dump(_level) ⇒ Object

Marshal support

[View source]

244
245
246
# File 'lib/sorbet/eraser/t/enum.rb', line 244

def _dump(_level)
  Marshal.dump(serialize)
end

#cloneObject

[View source]

84
85
86
# File 'lib/sorbet/eraser/t/enum.rb', line 84

def clone
  self
end

#dupObject

Enum instance methods ##

[View source]

80
81
82
# File 'lib/sorbet/eraser/t/enum.rb', line 80

def dup
  self
end

#inspectObject

[View source]

101
102
103
# File 'lib/sorbet/eraser/t/enum.rb', line 101

def inspect
  "#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
end

#serializeObject

[View source]

88
89
90
91
# File 'lib/sorbet/eraser/t/enum.rb', line 88

def serialize
  assert_bound!
  @serialized_val
end

#to_json(*args) ⇒ Object

[View source]

93
94
95
# File 'lib/sorbet/eraser/t/enum.rb', line 93

def to_json(*args)
  serialize.to_json(*args)
end

#to_sObject

[View source]

97
98
99
# File 'lib/sorbet/eraser/t/enum.rb', line 97

def to_s
  inspect
end

#to_strObject

NB: Do not call this method. This exists to allow for a safe migration path in places where enum values are compared directly against string values.

Ruby’s string has a weird quirk where ‘’my_string’ == obj` calls obj.==(‘my_string’) if obj responds to the ‘to_str` method. It does not actually call `to_str` however.

See ruby-doc.org/core-2.4.0/String.html#method-i-3D-3D

Raises:

  • (NoMethodError)
[View source]

121
122
123
124
# File 'lib/sorbet/eraser/t/enum.rb', line 121

def to_str
  msg = 'Implicit conversion of Enum instances to strings is not allowed. Call #serialize instead.'
  raise NoMethodError.new(msg)
end