literal 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/literal/array.rb +183 -1
  3. data/lib/literal/data.rb +1 -7
  4. data/lib/literal/deferred_type.rb +23 -0
  5. data/lib/literal/enum.rb +9 -2
  6. data/lib/literal/properties/schema.rb +1 -1
  7. data/lib/literal/property.rb +4 -4
  8. data/lib/literal/transforms.rb +142 -0
  9. data/lib/literal/tuple.rb +60 -0
  10. data/lib/literal/types/any_type.rb +2 -2
  11. data/lib/literal/types/boolean_type.rb +2 -2
  12. data/lib/literal/types/constraint_type.rb +4 -2
  13. data/lib/literal/types/falsy_type.rb +2 -2
  14. data/lib/literal/types/frozen_type.rb +35 -1
  15. data/lib/literal/types/hash_type.rb +22 -1
  16. data/lib/literal/types/interface_type.rb +29 -1
  17. data/lib/literal/types/intersection_type.rb +25 -0
  18. data/lib/literal/types/json_data_type.rb +34 -4
  19. data/lib/literal/types/map_type.rb +18 -1
  20. data/lib/literal/types/never_type.rb +18 -3
  21. data/lib/literal/types/nilable_type.rb +18 -1
  22. data/lib/literal/types/not_type.rb +22 -1
  23. data/lib/literal/types/range_type.rb +18 -1
  24. data/lib/literal/types/set_type.rb +18 -1
  25. data/lib/literal/types/truthy_type.rb +17 -3
  26. data/lib/literal/types/tuple_type.rb +19 -2
  27. data/lib/literal/types/union_type.rb +21 -4
  28. data/lib/literal/types/void_type.rb +13 -3
  29. data/lib/literal/types.rb +51 -44
  30. data/lib/literal/version.rb +1 -1
  31. data/lib/literal.rb +11 -65
  32. metadata +7 -12
  33. data/lib/literal/data_property.rb +0 -16
  34. data/lib/literal/types/callable_type.rb +0 -31
  35. data/lib/literal/types/float_type.rb +0 -10
  36. data/lib/literal/types/integer_type.rb +0 -10
  37. data/lib/literal/types/lambda_type.rb +0 -12
  38. data/lib/literal/types/procable_type.rb +0 -12
  39. data/lib/literal/types/string_type.rb +0 -10
  40. data/lib/literal/types/symbol_type.rb +0 -10
data/lib/literal/types.rb CHANGED
@@ -4,36 +4,34 @@ module Literal::Types
4
4
  autoload :AnyType, "literal/types/any_type"
5
5
  autoload :ArrayType, "literal/types/array_type"
6
6
  autoload :BooleanType, "literal/types/boolean_type"
7
- autoload :CallableType, "literal/types/callable_type"
8
7
  autoload :ClassType, "literal/types/class_type"
9
8
  autoload :ConstraintType, "literal/types/constraint_type"
9
+ autoload :DeferredType, "literal/deferred_type"
10
10
  autoload :DescendantType, "literal/types/descendant_type"
11
11
  autoload :EnumerableType, "literal/types/enumerable_type"
12
12
  autoload :FalsyType, "literal/types/falsy_type"
13
- autoload :FloatType, "literal/types/float_type"
14
13
  autoload :FrozenType, "literal/types/frozen_type"
15
14
  autoload :HashType, "literal/types/hash_type"
16
- autoload :IntegerType, "literal/types/integer_type"
17
15
  autoload :InterfaceType, "literal/types/interface_type"
18
16
  autoload :IntersectionType, "literal/types/intersection_type"
19
17
  autoload :JSONDataType, "literal/types/json_data_type"
20
- autoload :LambdaType, "literal/types/lambda_type"
21
18
  autoload :MapType, "literal/types/map_type"
22
19
  autoload :NeverType, "literal/types/never_type"
23
20
  autoload :NilableType, "literal/types/nilable_type"
24
21
  autoload :NotType, "literal/types/not_type"
25
- autoload :ProcableType, "literal/types/procable_type"
26
22
  autoload :RangeType, "literal/types/range_type"
27
23
  autoload :SetType, "literal/types/set_type"
28
- autoload :StringType, "literal/types/string_type"
29
- autoload :SymbolType, "literal/types/symbol_type"
30
24
  autoload :TruthyType, "literal/types/truthy_type"
31
25
  autoload :TupleType, "literal/types/tuple_type"
32
26
  autoload :UnionType, "literal/types/union_type"
33
27
  autoload :VoidType, "literal/types/void_type"
34
28
 
29
+ ProcableType = InterfaceType.new(:to_proc).freeze
30
+ CallableType = InterfaceType.new(:call).freeze
31
+ LambdaType = ConstraintType.new(Proc, lambda?: true).freeze
32
+
35
33
  NilableBooleanType = NilableType.new(BooleanType::Instance).freeze
36
- NilableCallableType = NilableType.new(CallableType::Instance).freeze
34
+ NilableCallableType = NilableType.new(CallableType).freeze
37
35
  NilableJSONDataType = NilableType.new(JSONDataType).freeze
38
36
  NilableLambdaType = NilableType.new(LambdaType).freeze
39
37
  NilableProcableType = NilableType.new(ProcableType).freeze
@@ -44,7 +42,7 @@ module Literal::Types
44
42
  end
45
43
 
46
44
  def _Any?
47
- VoidType
45
+ VoidType::Instance
48
46
  end
49
47
 
50
48
  # Matches if the value is an `Array` and all the elements match the given type.
@@ -55,7 +53,7 @@ module Literal::Types
55
53
  # Nilable version of `_Array`
56
54
  def _Array?(...)
57
55
  NilableType.new(
58
- ArrayType.new(...),
56
+ ArrayType.new(...)
59
57
  )
60
58
  end
61
59
 
@@ -71,7 +69,7 @@ module Literal::Types
71
69
 
72
70
  # Matches if the value responds to `#call`.
73
71
  def _Callable
74
- CallableType::Instance
72
+ CallableType
75
73
  end
76
74
 
77
75
  # Nilabl version of `_Callable`
@@ -87,7 +85,7 @@ module Literal::Types
87
85
  # Nilable version of `_Class`
88
86
  def _Class?(...)
89
87
  NilableType.new(
90
- ClassType.new(...),
88
+ ClassType.new(...)
91
89
  )
92
90
  end
93
91
 
@@ -101,10 +99,14 @@ module Literal::Types
101
99
  # Nilable version of `_Constraint`
102
100
  def _Constraint?(...)
103
101
  NilableType.new(
104
- ConstraintType.new(...),
102
+ ConstraintType.new(...)
105
103
  )
106
104
  end
107
105
 
106
+ def _Deferred(...)
107
+ DeferredType.new(...)
108
+ end
109
+
108
110
  # Matches if the value is a descendant of the given class.
109
111
  def _Descendant(...)
110
112
  DescendantType.new(...)
@@ -113,7 +115,7 @@ module Literal::Types
113
115
  # Nilable version of `_Descendant`
114
116
  def _Descendant?(...)
115
117
  NilableType.new(
116
- DescendantType.new(...),
118
+ DescendantType.new(...)
117
119
  )
118
120
  end
119
121
 
@@ -125,7 +127,7 @@ module Literal::Types
125
127
  # Nilable version of `_Enumerable`
126
128
  def _Enumerable?(...)
127
129
  NilableType.new(
128
- EnumerableType.new(...),
130
+ EnumerableType.new(...)
129
131
  )
130
132
  end
131
133
 
@@ -134,17 +136,17 @@ module Literal::Types
134
136
  FalsyType::Instance
135
137
  end
136
138
 
137
- # Matches if the value is a `Float` and matches the given constraint.
139
+ # Matches if the value is a `Float` and matches the given constraints.
138
140
  # You could use a `Range`, for example, as a constraint.
139
141
  # If you don't need a constraint, use `Float` instead of `_Float`.
140
142
  def _Float(...)
141
- FloatType.new(...)
143
+ _Constraint(Float, ...)
142
144
  end
143
145
 
144
146
  # Nilable version of `_Float`
145
147
  def _Float?(...)
146
- NilableType.new(
147
- FloatType.new(...),
148
+ _Nilable(
149
+ _Float(...)
148
150
  )
149
151
  end
150
152
 
@@ -156,7 +158,7 @@ module Literal::Types
156
158
  # Nilable version of `_Frozen`
157
159
  def _Frozen?(...)
158
160
  NilableType.new(
159
- FrozenType.new(...),
161
+ FrozenType.new(...)
160
162
  )
161
163
  end
162
164
 
@@ -168,7 +170,7 @@ module Literal::Types
168
170
  # Nilable version of `_Hash`
169
171
  def _Hash?(...)
170
172
  NilableType.new(
171
- HashType.new(...),
173
+ HashType.new(...)
172
174
  )
173
175
  end
174
176
 
@@ -178,13 +180,13 @@ module Literal::Types
178
180
  # @example
179
181
  # attribute :age, _Integer(18..127)
180
182
  def _Integer(...)
181
- IntegerType.new(...)
183
+ _Constraint(Integer, ...)
182
184
  end
183
185
 
184
186
  # Nilable version of `_Integer`
185
187
  def _Integer?(...)
186
- NilableType.new(
187
- IntegerType.new(...),
188
+ _Nilable(
189
+ _Integer(...)
188
190
  )
189
191
  end
190
192
 
@@ -196,7 +198,7 @@ module Literal::Types
196
198
  # Nilable version of `_Interface`
197
199
  def _Interface?(...)
198
200
  NilableType.new(
199
- InterfaceType.new(...),
201
+ InterfaceType.new(...)
200
202
  )
201
203
  end
202
204
 
@@ -208,13 +210,13 @@ module Literal::Types
208
210
  # Nilable version of `_Intersection`
209
211
  def _Intersection?(...)
210
212
  NilableType.new(
211
- IntersectionType.new(...),
213
+ IntersectionType.new(...)
212
214
  )
213
215
  end
214
216
 
215
217
  # Ensures the value is valid JSON data (i.e. it came from JSON.parse).
216
218
  def _JSONData
217
- JSONDataType
219
+ JSONDataType::Instance
218
220
  end
219
221
 
220
222
  # Nilable version of `_JSONData`
@@ -239,13 +241,13 @@ module Literal::Types
239
241
  # Nilable version of `_Map`
240
242
  def _Map?(...)
241
243
  NilableType.new(
242
- MapType.new(...),
244
+ MapType.new(...)
243
245
  )
244
246
  end
245
247
 
246
248
  # Never matches any value.
247
249
  def _Never
248
- NeverType
250
+ NeverType::Instance
249
251
  end
250
252
 
251
253
  # Matches if the value is either `nil` or the given type.
@@ -254,8 +256,13 @@ module Literal::Types
254
256
  end
255
257
 
256
258
  # Matches if the given type is *not* matched.
257
- def _Not(...)
258
- NotType.new(...)
259
+ def _Not(type)
260
+ case type
261
+ when NotType
262
+ type.type
263
+ else
264
+ NotType.new(type)
265
+ end
259
266
  end
260
267
 
261
268
  # Matches if the value is a `Proc` or responds to `#to_proc`.
@@ -276,7 +283,7 @@ module Literal::Types
276
283
  # Nilable version of `_Range`
277
284
  def _Range?(...)
278
285
  NilableType.new(
279
- RangeType.new(...),
286
+ RangeType.new(...)
280
287
  )
281
288
  end
282
289
 
@@ -288,38 +295,38 @@ module Literal::Types
288
295
  # Nilable version of `_Set`
289
296
  def _Set?(...)
290
297
  NilableType.new(
291
- SetType.new(...),
298
+ SetType.new(...)
292
299
  )
293
300
  end
294
301
 
295
302
  # Matches if the value is a `String` and matches the given constraints.
296
303
  # If you don't need any constraints, use `String` instead of `_String`.
297
304
  def _String(...)
298
- StringType.new(...)
305
+ _Constraint(String, ...)
299
306
  end
300
307
 
301
308
  # Nilable version of `_String`
302
309
  def _String?(...)
303
- NilableType.new(
304
- StringType.new(...),
310
+ _Nilable(
311
+ _String(...)
305
312
  )
306
313
  end
307
314
 
308
- # Matches if the value is a `Symbol` and matches the given constraint.
315
+ # Matches if the value is a `Symbol` and matches the given constraints.
309
316
  def _Symbol(...)
310
- SymbolType.new(...)
317
+ _Constraint(Symbol, ...)
311
318
  end
312
319
 
313
320
  # Nilable version of `_Symbol`
314
321
  def _Symbol?(...)
315
- NilableType.new(
316
- SymbolType.new(...),
322
+ _Nilable(
323
+ _Symbol(...)
317
324
  )
318
325
  end
319
326
 
320
327
  # Matches *"truthy"* values (anything except `nil` and `false`).
321
328
  def _Truthy
322
- TruthyType
329
+ TruthyType::Instance
323
330
  end
324
331
 
325
332
  # Matches if the value is an `Array` and each element matches the given types in order.
@@ -330,7 +337,7 @@ module Literal::Types
330
337
  # Nilable version of `_Typle`
331
338
  def _Tuple?(...)
332
339
  NilableType.new(
333
- TupleType.new(...),
340
+ TupleType.new(...)
334
341
  )
335
342
  end
336
343
 
@@ -342,11 +349,11 @@ module Literal::Types
342
349
  # Nilable version of `_Union`
343
350
  def _Union?(...)
344
351
  NilableType.new(
345
- UnionType.new(...),
352
+ UnionType.new(...)
346
353
  )
347
354
  end
348
355
 
349
356
  def _Void
350
- VoidType
357
+ VoidType::Instance
351
358
  end
352
359
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal
4
- VERSION = "1.2.0"
4
+ VERSION = "1.4.0"
5
5
  end
data/lib/literal.rb CHANGED
@@ -22,76 +22,14 @@ module Literal
22
22
  autoload :Struct, "literal/struct"
23
23
  autoload :Type, "literal/type"
24
24
  autoload :Types, "literal/types"
25
+ autoload :Tuple, "literal/tuple"
25
26
 
26
27
  # Errors
27
28
  autoload :Error, "literal/errors/error"
28
29
  autoload :TypeError, "literal/errors/type_error"
29
30
  autoload :ArgumentError, "literal/errors/argument_error"
30
31
 
31
- TRANSFORMS = {
32
- Integer => {
33
- abs: Integer,
34
- ceil: Integer,
35
- chr: String,
36
- denominator: Integer,
37
- even?: Types::BooleanType::Instance,
38
- floor: Integer,
39
- hash: Integer,
40
- inspect: String,
41
- integer?: true,
42
- magnitude: Integer,
43
- negative?: Types::BooleanType::Instance,
44
- next: Integer,
45
- nonzero?: Types::BooleanType::Instance,
46
- numerator: Integer,
47
- odd?: Types::BooleanType::Instance,
48
- ord: Integer,
49
- positive?: Types::BooleanType::Instance,
50
- pred: Integer,
51
- round: Integer,
52
- size: Integer,
53
- succ: Integer,
54
- to_f: Float,
55
- to_i: Integer,
56
- to_int: Integer,
57
- to_r: Rational,
58
- to_s: String,
59
- truncate: Integer,
60
- zero?: Types::BooleanType::Instance,
61
- },
62
- String => {
63
- ascii_only?: Types::BooleanType::Instance,
64
- bytesize: Integer,
65
- capitalize: String,
66
- chomp: String,
67
- chop: String,
68
- downcase: String,
69
- dump: String,
70
- empty?: Types::BooleanType::Instance,
71
- hash: Integer,
72
- inspect: String,
73
- length: Integer,
74
- lstrip: String,
75
- ord: Integer,
76
- reverse: String,
77
- rstrip: String,
78
- scrub: String,
79
- size: Integer,
80
- strip: String,
81
- swapcase: String,
82
- to_str: String,
83
- upcase: String,
84
- valid_encoding?: Types::BooleanType::Instance,
85
- },
86
- Array => {
87
- size: Integer,
88
- length: Integer,
89
- empty?: Types::BooleanType::Instance,
90
- sort: Array,
91
- to_a: Array,
92
- to_ary: Array,
93
- },
94
- }.transform_values! { |it| it.transform_keys(&:to_proc) }.freeze
32
+ autoload :TRANSFORMS, "literal/transforms"
95
33
 
96
34
  def self.Enum(type)
97
35
  Class.new(Literal::Enum) do
@@ -111,6 +49,10 @@ module Literal
111
49
  Literal::Hash::Generic.new(key_type, value_type)
112
50
  end
113
51
 
52
+ def self.Tuple(*types)
53
+ Literal::Tuple::Generic.new(*types)
54
+ end
55
+
114
56
  def self.check(actual:, expected:)
115
57
  if expected === actual
116
58
  true
@@ -123,9 +65,13 @@ module Literal
123
65
  end
124
66
 
125
67
  def self.subtype?(type, of:)
68
+ type = type.block.call if Types::DeferredType === type
69
+
126
70
  (of == type) || case of
127
- when Literal::Type, Module
71
+ when Literal::Type
128
72
  of >= type
73
+ when Module
74
+ (Module === type) ? of >= type : false
129
75
  when Range
130
76
  of.cover?(type)
131
77
  else
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: literal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-22 00:00:00.000000000 Z
11
+ date: 2024-12-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A literal Ruby gem.
13
+ description: Enums, properties, generics, structured objects and runtime type checking.
14
14
  email:
15
15
  - joel@drapper.me
16
16
  executables: []
@@ -22,8 +22,8 @@ files:
22
22
  - lib/literal.rb
23
23
  - lib/literal/array.rb
24
24
  - lib/literal/data.rb
25
- - lib/literal/data_property.rb
26
25
  - lib/literal/data_structure.rb
26
+ - lib/literal/deferred_type.rb
27
27
  - lib/literal/enum.rb
28
28
  - lib/literal/errors/argument_error.rb
29
29
  - lib/literal/errors/error.rb
@@ -44,34 +44,29 @@ files:
44
44
  - lib/literal/railtie.rb
45
45
  - lib/literal/set.rb
46
46
  - lib/literal/struct.rb
47
+ - lib/literal/transforms.rb
48
+ - lib/literal/tuple.rb
47
49
  - lib/literal/type.rb
48
50
  - lib/literal/types.rb
49
51
  - lib/literal/types/any_type.rb
50
52
  - lib/literal/types/array_type.rb
51
53
  - lib/literal/types/boolean_type.rb
52
- - lib/literal/types/callable_type.rb
53
54
  - lib/literal/types/class_type.rb
54
55
  - lib/literal/types/constraint_type.rb
55
56
  - lib/literal/types/descendant_type.rb
56
57
  - lib/literal/types/enumerable_type.rb
57
58
  - lib/literal/types/falsy_type.rb
58
- - lib/literal/types/float_type.rb
59
59
  - lib/literal/types/frozen_type.rb
60
60
  - lib/literal/types/hash_type.rb
61
- - lib/literal/types/integer_type.rb
62
61
  - lib/literal/types/interface_type.rb
63
62
  - lib/literal/types/intersection_type.rb
64
63
  - lib/literal/types/json_data_type.rb
65
- - lib/literal/types/lambda_type.rb
66
64
  - lib/literal/types/map_type.rb
67
65
  - lib/literal/types/never_type.rb
68
66
  - lib/literal/types/nilable_type.rb
69
67
  - lib/literal/types/not_type.rb
70
- - lib/literal/types/procable_type.rb
71
68
  - lib/literal/types/range_type.rb
72
69
  - lib/literal/types/set_type.rb
73
- - lib/literal/types/string_type.rb
74
- - lib/literal/types/symbol_type.rb
75
70
  - lib/literal/types/truthy_type.rb
76
71
  - lib/literal/types/tuple_type.rb
77
72
  - lib/literal/types/union_type.rb
@@ -83,7 +78,7 @@ licenses:
83
78
  metadata:
84
79
  homepage_uri: https://literal.fun
85
80
  source_code_uri: https://github.com/joeldrapper/literal
86
- changelog_uri: https://github.com/joeldrapper/literal/blob/main/CHANGELOG.md
81
+ changelog_uri: https://github.com/joeldrapper/literal/releases
87
82
  funding_uri: https://github.com/sponsors/joeldrapper
88
83
  rubygems_mfa_required: 'true'
89
84
  post_install_message:
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Literal::DataProperty < Literal::Property
4
- def generate_initializer_assign_value(buffer = +"")
5
- buffer <<
6
- "@" <<
7
- @name.name <<
8
- " = " <<
9
- escaped_name <<
10
- ".frozen? ? " <<
11
- escaped_name <<
12
- " : " <<
13
- escaped_name <<
14
- ".dup.freeze\n"
15
- end
16
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- class Literal::Types::CallableType
5
- include Literal::Type
6
-
7
- def inspect
8
- "_Callable"
9
- end
10
-
11
- def ===(value)
12
- value.respond_to?(:call)
13
- end
14
-
15
- def >=(other)
16
- (self == other) || (Proc == other) || (Method == other) || case other
17
- when Literal::Types::IntersectionType
18
- other.types.any? { |type| Literal.subtype?(type, of: self) }
19
- when Literal::Types::ConstraintType
20
- other.object_constraints.any? { |type| Literal.subtype?(type, of: self) }
21
- when Literal::Types::InterfaceType
22
- other.methods.include?(:call)
23
- else
24
- false
25
- end
26
- end
27
-
28
- Instance = new.freeze
29
-
30
- freeze
31
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- class Literal::Types::FloatType < Literal::Types::ConstraintType
5
- def inspect = "_Float(#{inspect_constraints})"
6
-
7
- def ===(value)
8
- Float === value && super
9
- end
10
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- class Literal::Types::IntegerType < Literal::Types::ConstraintType
5
- def inspect = "_Integer(#{inspect_constraints})"
6
-
7
- def ===(value)
8
- Integer === value && super
9
- end
10
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- module Literal::Types::LambdaType
5
- def self.inspect = "_Lambda"
6
-
7
- def self.===(value)
8
- Proc === value && value.lambda?
9
- end
10
-
11
- freeze
12
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- module Literal::Types::ProcableType
5
- def self.inspect = "_Procable"
6
-
7
- def self.===(value)
8
- Proc === value || value.respond_to?(:to_proc)
9
- end
10
-
11
- freeze
12
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- class Literal::Types::StringType < Literal::Types::ConstraintType
5
- def inspect = "_String(#{inspect_constraints})"
6
-
7
- def ===(value)
8
- String === value && super
9
- end
10
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- class Literal::Types::SymbolType < Literal::Types::ConstraintType
5
- def inspect = "_Symbol(#{inspect_constraints})"
6
-
7
- def ===(value)
8
- Symbol === value && super
9
- end
10
- end