literal 1.4.0 → 1.6.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.
@@ -6,6 +6,7 @@ class Literal::Types::EnumerableType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -12,6 +12,7 @@ class Literal::Types::FrozenType
12
12
  end
13
13
 
14
14
  @type = type
15
+ freeze
15
16
  end
16
17
 
17
18
  attr_reader :type
@@ -7,6 +7,7 @@ class Literal::Types::HashType
7
7
  def initialize(key_type, value_type)
8
8
  @key_type = key_type
9
9
  @value_type = value_type
10
+ freeze
10
11
  end
11
12
 
12
13
  attr_reader :key_type, :value_type
@@ -14,6 +14,7 @@ class Literal::Types::InterfaceType
14
14
  def initialize(*methods)
15
15
  raise Literal::ArgumentError.new("_Interface type must have at least one method.") if methods.size < 1
16
16
  @methods = methods
17
+ freeze
17
18
  end
18
19
 
19
20
  attr_reader :methods
@@ -8,6 +8,7 @@ class Literal::Types::IntersectionType
8
8
  raise Literal::ArgumentError.new("_Intersection type must have at least one type.") if types.size < 1
9
9
 
10
10
  @types = types
11
+ freeze
11
12
  end
12
13
 
13
14
  attr_reader :types
@@ -6,6 +6,7 @@ class Literal::Types::MapType
6
6
 
7
7
  def initialize(**shape)
8
8
  @shape = shape
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :shape
@@ -6,6 +6,7 @@ class Literal::Types::NilableType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -6,6 +6,7 @@ class Literal::Types::NotType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -6,6 +6,7 @@ class Literal::Types::RangeType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -6,6 +6,7 @@ class Literal::Types::SetType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -8,6 +8,7 @@ class Literal::Types::TupleType
8
8
  raise Literal::ArgumentError.new("_Tuple type must have at least one type.") if types.size < 1
9
9
 
10
10
  @types = types
11
+ freeze
11
12
  end
12
13
 
13
14
  attr_reader :types
@@ -3,23 +3,43 @@
3
3
  class Literal::Types::UnionType
4
4
  include Enumerable
5
5
 
6
- def initialize(*types)
7
- raise Literal::ArgumentError.new("_Union type must have at least one type.") if types.size < 1
6
+ def initialize(*queue)
7
+ raise Literal::ArgumentError.new("_Union type must have at least one type.") if queue.size < 1
8
+ types = []
9
+ primitives = Set[]
10
+
11
+ while queue.length > 0
12
+ type = queue.shift
13
+ case type
14
+ when Literal::Types::UnionType
15
+ queue.concat(type.types, type.primitives.to_a)
16
+ when Array, Hash, String, Symbol, Integer, Float, Complex, Rational, true, false, nil
17
+ primitives << type
18
+ else
19
+ types << type
20
+ end
21
+ end
22
+
23
+ types.uniq!
24
+ @types = types
25
+ @primitives = primitives
8
26
 
9
- @types = []
10
- load_types(types)
11
- @types.uniq!
12
27
  @types.freeze
28
+ @primitives.freeze
29
+ freeze
13
30
  end
14
31
 
15
- attr_reader :types
32
+ attr_reader :types, :primitives
16
33
 
17
34
  def inspect
18
35
  "_Union(#{@types.inspect})"
19
36
  end
20
37
 
21
38
  def ===(value)
39
+ return true if @primitives.include?(value)
40
+
22
41
  types = @types
42
+
23
43
  i, len = 0, types.size
24
44
  while i < len
25
45
  return true if types[i] === value
@@ -28,48 +48,49 @@ class Literal::Types::UnionType
28
48
  end
29
49
 
30
50
  def each(&)
51
+ @primitives.each(&)
31
52
  @types.each(&)
32
53
  end
33
54
 
34
55
  def deconstruct
35
- @types.to_a
56
+ to_a
36
57
  end
37
58
 
38
59
  def [](key)
39
- if @types.include?(key)
60
+ if @primitives.include?(key) || @types.include?(key)
40
61
  key
41
- else
42
- raise ArgumentError.new("#{key} not in #{inspect}")
43
62
  end
44
63
  end
45
64
 
65
+ def fetch(key)
66
+ self[key] or raise KeyError.new("Key not found: #{key.inspect}")
67
+ end
68
+
46
69
  def record_literal_type_errors(ctx)
47
- @types.each do |type|
70
+ each do |type|
48
71
  ctx.add_child(label: type.inspect, expected: type, actual: ctx.actual)
49
72
  end
73
+
50
74
  ctx.children.clear if ctx.children.none? { |c| c.children.any? }
51
75
  end
52
76
 
53
77
  def >=(other)
78
+ types = @types
79
+ primitives = @primitives
80
+
54
81
  case other
55
82
  when Literal::Types::UnionType
56
- other.types.all? do |other_type|
57
- @types.any? do |type|
58
- Literal.subtype?(type, of: other_type)
59
- end
60
- end
61
- else
62
- @types.any? do |type|
63
- Literal.subtype?(other, of: type)
83
+ types_have_at_least_one_subtype = other.types.all? do |other_type|
84
+ primitives.any? { |p| Literal.subtype?(p, of: other_type) } || types.any? { |t| Literal.subtype?(t, of: other_type) }
64
85
  end
65
- end
66
- end
67
86
 
68
- private
87
+ primitives_have_at_least_one_subtype = other.primitives.all? do |other_primitive|
88
+ primitives.any? { |p| Literal.subtype?(p, of: other_primitive) } || types.any? { |t| Literal.subtype?(t, of: other_primitive) }
89
+ end
69
90
 
70
- def load_types(types)
71
- types.each do |type|
72
- (Literal::Types::UnionType === type) ? load_types(type.types) : @types << type
91
+ types_have_at_least_one_subtype && primitives_have_at_least_one_subtype
92
+ else
93
+ types.any? { |t| Literal.subtype?(other, of: t) } || primitives.any? { |p| Literal.subtype?(other, of: p) }
73
94
  end
74
95
  end
75
96
 
data/lib/literal/types.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal::Types
4
+ extend self
5
+
4
6
  autoload :AnyType, "literal/types/any_type"
5
7
  autoload :ArrayType, "literal/types/array_type"
6
8
  autoload :BooleanType, "literal/types/boolean_type"
@@ -26,112 +28,175 @@ module Literal::Types
26
28
  autoload :UnionType, "literal/types/union_type"
27
29
  autoload :VoidType, "literal/types/void_type"
28
30
 
29
- ProcableType = InterfaceType.new(:to_proc).freeze
30
- CallableType = InterfaceType.new(:call).freeze
31
- LambdaType = ConstraintType.new(Proc, lambda?: true).freeze
32
-
33
- NilableBooleanType = NilableType.new(BooleanType::Instance).freeze
34
- NilableCallableType = NilableType.new(CallableType).freeze
35
- NilableJSONDataType = NilableType.new(JSONDataType).freeze
36
- NilableLambdaType = NilableType.new(LambdaType).freeze
37
- NilableProcableType = NilableType.new(ProcableType).freeze
38
-
39
31
  # Matches any value except `nil`. Use `_Any?` or `_Void` to match any value including `nil`.
32
+ # ```ruby
33
+ # _Any
34
+ # ```
40
35
  def _Any
41
36
  AnyType::Instance
42
37
  end
43
38
 
39
+ # Matches any value including `nil`. This is the same as `_Void` and the opposite of `_Never`.
40
+ # ```ruby
41
+ # _Any?
42
+ # ```
44
43
  def _Any?
45
44
  VoidType::Instance
46
45
  end
47
46
 
48
- # Matches if the value is an `Array` and all the elements match the given type.
49
- def _Array(...)
50
- ArrayType.new(...)
47
+ # Matches if the value is an `Array` and all the elements of the array match the given type.
48
+ # ```ruby
49
+ # _Array(String)
50
+ # ```
51
+ def _Array(type)
52
+ ArrayType.new(type)
51
53
  end
52
54
 
53
- # Nilable version of `_Array`
54
- def _Array?(...)
55
- NilableType.new(
56
- ArrayType.new(...)
55
+ # Nilable version of `_Array`.
56
+ # ```ruby
57
+ # _Array?(String)
58
+ # ```
59
+ def _Array?(type)
60
+ _Nilable(
61
+ _Array(type)
57
62
  )
58
63
  end
59
64
 
60
- # Matches if the value is `true` or `false`.
65
+ # Matches if the value is either `true` or `false`. This is equivalent to `_Union(true, false)`.
66
+ # ```ruby
67
+ # _Boolean
68
+ # ```
61
69
  def _Boolean
62
70
  BooleanType::Instance
63
71
  end
64
72
 
65
- # Nilable version of `_Boolean`
73
+ # Nilable version of `_Boolean`.
74
+ # ```ruby
75
+ # _Boolean?
76
+ # ```
66
77
  def _Boolean?
67
78
  NilableBooleanType
68
79
  end
69
80
 
70
81
  # Matches if the value responds to `#call`.
82
+ # ```ruby
83
+ # _Callable
84
+ # ```
71
85
  def _Callable
72
86
  CallableType
73
87
  end
74
88
 
75
- # Nilabl version of `_Callable`
89
+ # Nilable version of `_Callable`.
90
+ # ```ruby
91
+ # _Callable?
92
+ # ```
76
93
  def _Callable?
77
94
  NilableCallableType
78
95
  end
79
96
 
80
97
  # Matches if the value either the given class or a subclass of it.
81
- def _Class(...)
82
- ClassType.new(...)
98
+ # ```ruby
99
+ # _Class(ActiveRecord::Base)
100
+ # ```
101
+ def _Class(expected_class)
102
+ ClassType.new(expected_class)
83
103
  end
84
104
 
85
- # Nilable version of `_Class`
105
+ # Nilable version of `_Class`.
106
+ # ```ruby
107
+ # _Class?(ActiveRecord::Base)
108
+ # ```
86
109
  def _Class?(...)
87
- NilableType.new(
88
- ClassType.new(...)
110
+ _Nilable(
111
+ _Class(...)
89
112
  )
90
113
  end
91
114
 
92
115
  # Similar to `_Intersection`, but allows you to specify attribute constraints as keyword arguments.
93
- # @example
94
- # _Constraint(Array, size: 1..3)
95
- def _Constraint(...)
96
- ConstraintType.new(...)
116
+ # ```ruby
117
+ # _Constraint(Array, size: 1..3)
118
+ # ```
119
+ def _Constraint(*a, **k)
120
+ if a.length == 1 && k.length == 0
121
+ a[0]
122
+ else
123
+ ConstraintType.new(*a, **k)
124
+ end
97
125
  end
98
126
 
99
127
  # Nilable version of `_Constraint`
128
+ # ```ruby
129
+ # _Constraint?(Array, size: 1..3)
130
+ # ```
100
131
  def _Constraint?(...)
101
- NilableType.new(
102
- ConstraintType.new(...)
132
+ _Nilable(
133
+ _Constraint(...)
103
134
  )
104
135
  end
105
136
 
106
- def _Deferred(...)
107
- DeferredType.new(...)
137
+ # Matches if the value is a `Date` and matches the given constraints.
138
+ # If you don't need any constraints, use `Date` instead of `_Date`. See also `_Constraint`.
139
+ # ```ruby
140
+ # _Date((Date.today)..)
141
+ # _Date(year: 2025)
142
+ # ```
143
+ def _Date(...)
144
+ _Constraint(Date, ...)
145
+ end
146
+
147
+ # Nilable version of `_Date`.
148
+ def _Date?(...)
149
+ _Nilable(
150
+ _Date(...)
151
+ )
152
+ end
153
+
154
+ # Takes a type as a block so it can be resolved when needed. This is useful if declaring your type now would cause an error because constants haven’t been defined yet.
155
+ # ```ruby
156
+ # _Deferred { _Class(SomeFutureConstant) }
157
+ # ```
158
+ def _Deferred(&type)
159
+ DeferredType.new(&type)
160
+ end
161
+
162
+ # Nilable version of `_Deferred`.
163
+ def _Deferred?(&type)
164
+ _Nilable(
165
+ _Deferred(&type)
166
+ )
108
167
  end
109
168
 
110
169
  # Matches if the value is a descendant of the given class.
170
+ # ```ruby
171
+ # _Descendant(ActiveRecord::Base)
172
+ # ```
111
173
  def _Descendant(...)
112
174
  DescendantType.new(...)
113
175
  end
114
176
 
115
- # Nilable version of `_Descendant`
177
+ # Nilable version of `_Descendant`.
116
178
  def _Descendant?(...)
117
- NilableType.new(
118
- DescendantType.new(...)
179
+ _Nilable(
180
+ _Descendant(...)
119
181
  )
120
182
  end
121
183
 
122
184
  #  Matches if the value is an `Enumerable` and all its elements match the given type.
123
- def _Enumerable(...)
124
- EnumerableType.new(...)
185
+ # ```ruby
186
+ # _Enumerable(String)
187
+ # ```
188
+ def _Enumerable(type)
189
+ EnumerableType.new(type)
125
190
  end
126
191
 
127
- # Nilable version of `_Enumerable`
192
+ # Nilable version of `_Enumerable`.
128
193
  def _Enumerable?(...)
129
- NilableType.new(
130
- EnumerableType.new(...)
194
+ _Nilable(
195
+ _Enumerable(...)
131
196
  )
132
197
  end
133
198
 
134
- # Matches *"falsy"* values (`nil` and `false`).
199
+ # Matches *"falsy"* values (`nil` and `false`). This is equivalent to `_Nilable(false)` or `_Union(nil, false)`.
135
200
  def _Falsy
136
201
  FalsyType::Instance
137
202
  end
@@ -139,11 +204,14 @@ module Literal::Types
139
204
  # Matches if the value is a `Float` and matches the given constraints.
140
205
  # You could use a `Range`, for example, as a constraint.
141
206
  # If you don't need a constraint, use `Float` instead of `_Float`.
207
+ # ```ruby
208
+ # _Float(5..10)
209
+ # ```
142
210
  def _Float(...)
143
211
  _Constraint(Float, ...)
144
212
  end
145
213
 
146
- # Nilable version of `_Float`
214
+ # Nilable version of `_Float`.
147
215
  def _Float?(...)
148
216
  _Nilable(
149
217
  _Float(...)
@@ -157,8 +225,8 @@ module Literal::Types
157
225
 
158
226
  # Nilable version of `_Frozen`
159
227
  def _Frozen?(...)
160
- NilableType.new(
161
- FrozenType.new(...)
228
+ _Nilable(
229
+ _Frozen(...)
162
230
  )
163
231
  end
164
232
 
@@ -169,8 +237,8 @@ module Literal::Types
169
237
 
170
238
  # Nilable version of `_Hash`
171
239
  def _Hash?(...)
172
- NilableType.new(
173
- HashType.new(...)
240
+ _Nilable(
241
+ _Hash(...)
174
242
  )
175
243
  end
176
244
 
@@ -197,8 +265,8 @@ module Literal::Types
197
265
 
198
266
  # Nilable version of `_Interface`
199
267
  def _Interface?(...)
200
- NilableType.new(
201
- InterfaceType.new(...)
268
+ _Nilable(
269
+ _Interface(...)
202
270
  )
203
271
  end
204
272
 
@@ -209,8 +277,8 @@ module Literal::Types
209
277
 
210
278
  # Nilable version of `_Intersection`
211
279
  def _Intersection?(...)
212
- NilableType.new(
213
- IntersectionType.new(...)
280
+ _Nilable(
281
+ _Intersection(...)
214
282
  )
215
283
  end
216
284
 
@@ -234,14 +302,20 @@ module Literal::Types
234
302
  NilableLambdaType
235
303
  end
236
304
 
305
+ # ```ruby
306
+ # _Map(name: String, age: Integer)
307
+ # ```
237
308
  def _Map(...)
238
309
  MapType.new(...)
239
310
  end
240
311
 
241
312
  # Nilable version of `_Map`
313
+ # ```ruby
314
+ # _Map?(name: String, age: Integer)
315
+ # ```
242
316
  def _Map?(...)
243
- NilableType.new(
244
- MapType.new(...)
317
+ _Nilable(
318
+ _Map(...)
245
319
  )
246
320
  end
247
321
 
@@ -282,8 +356,8 @@ module Literal::Types
282
356
 
283
357
  # Nilable version of `_Range`
284
358
  def _Range?(...)
285
- NilableType.new(
286
- RangeType.new(...)
359
+ _Nilable(
360
+ _Range(...)
287
361
  )
288
362
  end
289
363
 
@@ -294,8 +368,8 @@ module Literal::Types
294
368
 
295
369
  # Nilable version of `_Set`
296
370
  def _Set?(...)
297
- NilableType.new(
298
- SetType.new(...)
371
+ _Nilable(
372
+ _Set(...)
299
373
  )
300
374
  end
301
375
 
@@ -324,20 +398,39 @@ module Literal::Types
324
398
  )
325
399
  end
326
400
 
401
+ # Matches if the value is a `Time` and matches the given constraints.
402
+ # If you don't need any constraints, use `Time` instead of `_Time`.
403
+ def _Time(...)
404
+ _Constraint(Time, ...)
405
+ end
406
+
407
+ # Nilable version of `_Time`
408
+ def _Time?(...)
409
+ _Nilable(
410
+ _Time(...)
411
+ )
412
+ end
413
+
327
414
  # Matches *"truthy"* values (anything except `nil` and `false`).
328
415
  def _Truthy
329
416
  TruthyType::Instance
330
417
  end
331
418
 
332
419
  # Matches if the value is an `Array` and each element matches the given types in order.
420
+ # ```ruby
421
+ # _Tuple(String, Integer, Integer)
422
+ # ```
333
423
  def _Tuple(...)
334
424
  TupleType.new(...)
335
425
  end
336
426
 
337
427
  # Nilable version of `_Typle`
428
+ # ```ruby
429
+ # _Tuple?(String, Integer, Integer)
430
+ # ```
338
431
  def _Tuple?(...)
339
- NilableType.new(
340
- TupleType.new(...)
432
+ _Nilable(
433
+ _Tuple(...)
341
434
  )
342
435
  end
343
436
 
@@ -348,12 +441,22 @@ module Literal::Types
348
441
 
349
442
  # Nilable version of `_Union`
350
443
  def _Union?(...)
351
- NilableType.new(
352
- UnionType.new(...)
444
+ _Nilable(
445
+ _Union(...)
353
446
  )
354
447
  end
355
448
 
356
449
  def _Void
357
450
  VoidType::Instance
358
451
  end
452
+
453
+ ProcableType = _Interface(:to_proc)
454
+ CallableType = _Interface(:call)
455
+ LambdaType = _Constraint(Proc, lambda?: true)
456
+
457
+ NilableBooleanType = _Nilable(BooleanType::Instance)
458
+ NilableCallableType = _Nilable(CallableType)
459
+ NilableJSONDataType = _Nilable(JSONDataType)
460
+ NilableLambdaType = _Nilable(LambdaType)
461
+ NilableProcableType = _Nilable(ProcableType)
359
462
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal
4
- VERSION = "1.4.0"
4
+ VERSION = "1.6.0"
5
5
  end
data/lib/literal.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal
4
- TYPE_CHECKS_DISABLED = ENV["LITERAL_TYPE_CHECKS"] == "false"
5
-
6
4
  autoload :Array, "literal/array"
7
5
  autoload :Data, "literal/data"
8
6
  autoload :DataProperty, "literal/data_property"
@@ -33,7 +31,7 @@ module Literal
33
31
 
34
32
  def self.Enum(type)
35
33
  Class.new(Literal::Enum) do
36
- prop :value, type, :positional
34
+ prop :value, type, :positional, reader: :public
37
35
  end
38
36
  end
39
37
 
@@ -65,15 +63,35 @@ module Literal
65
63
  end
66
64
 
67
65
  def self.subtype?(type, of:)
68
- type = type.block.call if Types::DeferredType === type
66
+ supertype = of
67
+ subtype = type
68
+
69
+ subtype = subtype.block.call if Types::DeferredType === subtype
70
+
71
+ return true if supertype == subtype
69
72
 
70
- (of == type) || case of
73
+ case supertype
71
74
  when Literal::Type
72
- of >= type
75
+ supertype >= subtype
73
76
  when Module
74
- (Module === type) ? of >= type : false
77
+ case subtype
78
+ when Module
79
+ supertype >= subtype
80
+ when Numeric
81
+ Numeric >= supertype
82
+ when String
83
+ String >= supertype
84
+ when Symbol
85
+ Symbol >= supertype
86
+ when ::Array
87
+ ::Array >= supertype
88
+ when ::Hash
89
+ ::Hash >= supertype
90
+ else
91
+ false
92
+ end
75
93
  when Range
76
- of.cover?(type)
94
+ supertype.cover?(subtype)
77
95
  else
78
96
  false
79
97
  end