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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/literal/array.rb +124 -2
- data/lib/literal/enum.rb +76 -34
- data/lib/literal/failure.rb +7 -0
- data/lib/literal/null.rb +4 -0
- data/lib/literal/properties/schema.rb +1 -1
- data/lib/literal/properties.rb +2 -2
- data/lib/literal/property.rb +13 -27
- data/lib/literal/rails/enum_type.rb +9 -11
- data/lib/literal/rails/patches/active_record.rb +4 -3
- data/lib/literal/railtie.rb +8 -14
- data/lib/literal/result.rb +4 -0
- data/lib/literal/success.rb +7 -0
- data/lib/literal/transforms.rb +1 -0
- data/lib/literal/tuple.rb +12 -0
- data/lib/literal/types/array_type.rb +1 -0
- data/lib/literal/types/class_type.rb +1 -0
- data/lib/literal/types/constraint_type.rb +8 -2
- data/lib/literal/types/descendant_type.rb +1 -0
- data/lib/literal/types/enumerable_type.rb +1 -0
- data/lib/literal/types/frozen_type.rb +1 -0
- data/lib/literal/types/hash_type.rb +1 -0
- data/lib/literal/types/interface_type.rb +1 -0
- data/lib/literal/types/intersection_type.rb +1 -0
- data/lib/literal/types/map_type.rb +1 -0
- data/lib/literal/types/nilable_type.rb +1 -0
- data/lib/literal/types/not_type.rb +1 -0
- data/lib/literal/types/range_type.rb +1 -0
- data/lib/literal/types/set_type.rb +1 -0
- data/lib/literal/types/tuple_type.rb +1 -0
- data/lib/literal/types/union_type.rb +46 -25
- data/lib/literal/types.rb +164 -61
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +26 -8
- metadata +6 -6
@@ -3,23 +3,43 @@
|
|
3
3
|
class Literal::Types::UnionType
|
4
4
|
include Enumerable
|
5
5
|
|
6
|
-
def initialize(*
|
7
|
-
raise Literal::ArgumentError.new("_Union type must have at least one type.") if
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
71
|
-
|
72
|
-
(
|
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
|
-
|
50
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
-
#
|
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
|
-
|
82
|
-
|
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
|
-
|
88
|
-
|
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
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
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
|
-
|
102
|
-
|
132
|
+
_Nilable(
|
133
|
+
_Constraint(...)
|
103
134
|
)
|
104
135
|
end
|
105
136
|
|
106
|
-
|
107
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
124
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
161
|
-
|
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
|
-
|
173
|
-
|
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
|
-
|
201
|
-
|
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
|
-
|
213
|
-
|
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
|
-
|
244
|
-
|
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
|
-
|
286
|
-
|
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
|
-
|
298
|
-
|
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
|
-
|
340
|
-
|
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
|
-
|
352
|
-
|
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
|
data/lib/literal/version.rb
CHANGED
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
|
-
|
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
|
-
|
73
|
+
case supertype
|
71
74
|
when Literal::Type
|
72
|
-
|
75
|
+
supertype >= subtype
|
73
76
|
when Module
|
74
|
-
|
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
|
-
|
94
|
+
supertype.cover?(subtype)
|
77
95
|
else
|
78
96
|
false
|
79
97
|
end
|