literal 1.1.0 → 1.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/literal/array.rb +428 -0
- data/lib/literal/data_structure.rb +0 -10
- data/lib/literal/flags.rb +17 -4
- data/lib/literal/hash.rb +48 -0
- data/lib/literal/properties/schema.rb +1 -1
- data/lib/literal/properties.rb +5 -0
- data/lib/literal/rails/flags_type.rb +41 -0
- data/lib/literal/rails.rb +1 -0
- data/lib/literal/railtie.rb +8 -0
- data/lib/literal/set.rb +48 -0
- data/lib/literal/struct.rb +26 -0
- data/lib/literal/type.rb +7 -0
- data/lib/literal/types/any_type.rb +13 -3
- data/lib/literal/types/array_type.rb +18 -1
- data/lib/literal/types/boolean_type.rb +18 -3
- data/lib/literal/types/callable_type.rb +22 -3
- data/lib/literal/types/class_type.rb +20 -1
- data/lib/literal/types/constraint_type.rb +40 -1
- data/lib/literal/types/descendant_type.rb +18 -1
- data/lib/literal/types/enumerable_type.rb +18 -1
- data/lib/literal/types/falsy_type.rb +22 -3
- data/lib/literal/types/interface_type.rb +2 -0
- data/lib/literal/types/intersection_type.rb +2 -0
- data/lib/literal/types/json_data_type.rb +16 -0
- data/lib/literal/types/map_type.rb +6 -5
- data/lib/literal/types/nilable_type.rb +6 -0
- data/lib/literal/types/set_type.rb +10 -0
- data/lib/literal/types/union_type.rb +7 -0
- data/lib/literal/types.rb +12 -12
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +97 -5
- metadata +7 -2
data/lib/literal/struct.rb
CHANGED
@@ -6,4 +6,30 @@ class Literal::Struct < Literal::DataStructure
|
|
6
6
|
super
|
7
7
|
end
|
8
8
|
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
case key
|
12
|
+
when Symbol
|
13
|
+
when String
|
14
|
+
key = key.intern
|
15
|
+
else
|
16
|
+
raise TypeError.new("expected a string or symbol, got #{key.inspect.class}")
|
17
|
+
end
|
18
|
+
|
19
|
+
prop = self.class.literal_properties[key] || raise(NameError.new("unknown attribute: #{key.inspect} for #{self.class}"))
|
20
|
+
__send__(prop.name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(key, value)
|
24
|
+
case key
|
25
|
+
when Symbol
|
26
|
+
when String
|
27
|
+
key = key.intern
|
28
|
+
else
|
29
|
+
raise TypeError.new("expected a string or symbol, got #{key.inspect.class}")
|
30
|
+
end
|
31
|
+
|
32
|
+
prop = self.class.literal_properties[key] || raise(NameError.new("unknown attribute: #{key.inspect} for #{self.class}"))
|
33
|
+
__send__(:"#{prop.name}=", value)
|
34
|
+
end
|
9
35
|
end
|
data/lib/literal/type.rb
ADDED
@@ -1,12 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::AnyType
|
5
|
+
include Literal::Type
|
6
6
|
|
7
|
-
def
|
7
|
+
def inspect
|
8
|
+
"_Any"
|
9
|
+
end
|
10
|
+
|
11
|
+
def ===(value)
|
8
12
|
!(nil === value)
|
9
13
|
end
|
10
14
|
|
15
|
+
def >=(other)
|
16
|
+
!(other === nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
Instance = new.freeze
|
20
|
+
|
11
21
|
freeze
|
12
22
|
end
|
@@ -2,16 +2,31 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::ArrayType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(type)
|
6
8
|
@type = type
|
7
9
|
end
|
8
10
|
|
9
|
-
|
11
|
+
attr_reader :type
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"_Array(#{@type.inspect})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
Array === value && value.all?(@type)
|
13
19
|
end
|
14
20
|
|
21
|
+
def >=(other)
|
22
|
+
case other
|
23
|
+
when Literal::Types::ArrayType
|
24
|
+
@type >= other.type
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
15
30
|
def record_literal_type_errors(context)
|
16
31
|
unless Array === context.actual
|
17
32
|
return
|
@@ -23,4 +38,6 @@ class Literal::Types::ArrayType
|
|
23
38
|
end
|
24
39
|
end
|
25
40
|
end
|
41
|
+
|
42
|
+
freeze
|
26
43
|
end
|
@@ -1,12 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::BooleanType
|
5
|
+
include Literal::Type
|
6
6
|
|
7
|
-
def
|
7
|
+
def inspect
|
8
|
+
"_Boolean"
|
9
|
+
end
|
10
|
+
|
11
|
+
def ===(value)
|
8
12
|
true == value || false == value
|
9
13
|
end
|
10
14
|
|
15
|
+
def >=(other)
|
16
|
+
case other
|
17
|
+
when true, false, Literal::Types::BooleanType
|
18
|
+
true
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Instance = new.freeze
|
25
|
+
|
11
26
|
freeze
|
12
27
|
end
|
@@ -1,12 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::CallableType
|
5
|
+
include Literal::Type
|
6
6
|
|
7
|
-
def
|
7
|
+
def inspect
|
8
|
+
"_Callable"
|
9
|
+
end
|
10
|
+
|
11
|
+
def ===(value)
|
8
12
|
value.respond_to?(:call)
|
9
13
|
end
|
10
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
|
+
|
11
30
|
freeze
|
12
31
|
end
|
@@ -2,13 +2,32 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::ClassType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(type)
|
6
8
|
@type = type
|
7
9
|
end
|
8
10
|
|
9
|
-
|
11
|
+
attr_reader :type
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"_Class(#{@type.name})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
Class === value && (value == @type || value < @type)
|
13
19
|
end
|
20
|
+
|
21
|
+
def >=(other)
|
22
|
+
case other
|
23
|
+
when Literal::Types::ClassType
|
24
|
+
Literal.subtype?(other.type, of: @type)
|
25
|
+
when Literal::Types::DescendantType
|
26
|
+
(Class === other.type) && Literal.subtype?(other.type, of: @type)
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
freeze
|
14
33
|
end
|
@@ -2,12 +2,19 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::ConstraintType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(*object_constraints, **property_constraints)
|
6
8
|
@object_constraints = object_constraints
|
7
9
|
@property_constraints = property_constraints
|
8
10
|
end
|
9
11
|
|
10
|
-
|
12
|
+
attr_reader :object_constraints
|
13
|
+
attr_reader :property_constraints
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"_Constraint(#{inspect_constraints})"
|
17
|
+
end
|
11
18
|
|
12
19
|
def ===(value)
|
13
20
|
object_constraints = @object_constraints
|
@@ -20,11 +27,40 @@ class Literal::Types::ConstraintType
|
|
20
27
|
|
21
28
|
@property_constraints.each do |a, t|
|
22
29
|
return false unless t === value.public_send(a)
|
30
|
+
rescue NoMethodError => e
|
31
|
+
raise unless e.name == a && e.receiver == value
|
32
|
+
return false
|
23
33
|
end
|
24
34
|
|
25
35
|
true
|
26
36
|
end
|
27
37
|
|
38
|
+
def >=(other)
|
39
|
+
case other
|
40
|
+
when Literal::Types::ConstraintType
|
41
|
+
other_object_constraints = other.object_constraints
|
42
|
+
return false unless @object_constraints.all? do |constraint|
|
43
|
+
other_object_constraints.all? { |c| Literal.subtype?(c, of: constraint) }
|
44
|
+
end
|
45
|
+
|
46
|
+
other_property_constraints = other.property_constraints
|
47
|
+
return false unless @property_constraints.all? do |k, v|
|
48
|
+
Literal.subtype?(other_property_constraints[k], of: v)
|
49
|
+
end
|
50
|
+
|
51
|
+
true
|
52
|
+
when Literal::Types::IntersectionType
|
53
|
+
other_object_constraints = other.types
|
54
|
+
return false unless @object_constraints.all? do |constraint|
|
55
|
+
other_object_constraints.all? { |c| Literal.subtype?(c, of: constraint) }
|
56
|
+
end
|
57
|
+
|
58
|
+
true
|
59
|
+
else
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
28
64
|
def record_literal_type_errors(context)
|
29
65
|
@object_constraints.each do |constraint|
|
30
66
|
next if constraint === context.actual
|
@@ -33,6 +69,7 @@ class Literal::Types::ConstraintType
|
|
33
69
|
end
|
34
70
|
|
35
71
|
@property_constraints.each do |property, constraint|
|
72
|
+
next unless context.actual.respond_to?(property)
|
36
73
|
actual = context.actual.public_send(property)
|
37
74
|
next if constraint === actual
|
38
75
|
|
@@ -57,4 +94,6 @@ class Literal::Types::ConstraintType
|
|
57
94
|
@property_constraints.map { |k, t| "#{k}: #{t.inspect}" }.join(", ")
|
58
95
|
end
|
59
96
|
end
|
97
|
+
|
98
|
+
freeze
|
60
99
|
end
|
@@ -1,13 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Literal::Types::DescendantType
|
4
|
+
include Literal::Type
|
5
|
+
|
4
6
|
def initialize(type)
|
5
7
|
@type = type
|
6
8
|
end
|
7
9
|
|
8
|
-
|
10
|
+
attr_reader :type
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"_Descendant(#{@type})"
|
14
|
+
end
|
9
15
|
|
10
16
|
def ===(value)
|
11
17
|
Module === value && value < @type
|
12
18
|
end
|
19
|
+
|
20
|
+
def >=(other)
|
21
|
+
case other
|
22
|
+
when Literal::Types::DescendantType, Literal::Types::ClassType
|
23
|
+
Literal.subtype?(other.type, of: @type)
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
freeze
|
13
30
|
end
|
@@ -2,13 +2,30 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::EnumerableType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(type)
|
6
8
|
@type = type
|
7
9
|
end
|
8
10
|
|
9
|
-
|
11
|
+
attr_reader :type
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"_Enumerable(#{@type.inspect})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
Enumerable === value && value.all?(@type)
|
13
19
|
end
|
20
|
+
|
21
|
+
def >=(other)
|
22
|
+
case other
|
23
|
+
when Literal::Types::EnumerableType
|
24
|
+
Literal.subtype?(other.type, of: @type)
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
freeze
|
14
31
|
end
|
@@ -1,12 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::FalsyType
|
5
|
+
include Literal::Type
|
6
6
|
|
7
|
-
def
|
7
|
+
def initialize
|
8
|
+
freeze
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"_Falsy"
|
13
|
+
end
|
14
|
+
|
15
|
+
def ===(value)
|
8
16
|
!value
|
9
17
|
end
|
10
18
|
|
19
|
+
def >=(other)
|
20
|
+
case other
|
21
|
+
when Literal::Types::FalsyType, nil, false
|
22
|
+
true
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Instance = new.freeze
|
29
|
+
|
11
30
|
freeze
|
12
31
|
end
|
@@ -19,5 +19,21 @@ module Literal::Types::JSONDataType
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.record_literal_type_errors(context)
|
23
|
+
case value = context.actual
|
24
|
+
when String, Integer, Float, true, false, nil
|
25
|
+
# nothing to do
|
26
|
+
when Hash
|
27
|
+
value.each do |k, v|
|
28
|
+
context.add_child(label: "[]", expected: String, actual: k) unless String === k
|
29
|
+
context.add_child(label: "[#{k.inspect}]", expected: self, actual: v) unless self === v
|
30
|
+
end
|
31
|
+
when Array
|
32
|
+
value.each_with_index do |item, index|
|
33
|
+
context.add_child(label: "[#{index}]", expected: self, actual: item) unless self === item
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
22
38
|
freeze
|
23
39
|
end
|
@@ -21,14 +21,15 @@ class Literal::Types::MapType
|
|
21
21
|
return
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
unless (expected
|
26
|
-
context.add_child(label: "[]", expected:
|
24
|
+
@shape.each do |key, expected|
|
25
|
+
unless context.actual.key?(key) || expected === nil
|
26
|
+
context.add_child(label: "[]", expected: key, actual: nil)
|
27
27
|
next
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
actual = context.actual[key]
|
31
|
+
unless expected === actual
|
32
|
+
context.add_child(label: "[#{key.inspect}]", expected:, actual:)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -17,4 +17,14 @@ class Literal::Types::SetType
|
|
17
17
|
|
18
18
|
true
|
19
19
|
end
|
20
|
+
|
21
|
+
def record_literal_type_errors(context)
|
22
|
+
return unless Set === context.actual
|
23
|
+
|
24
|
+
context.actual.each do |actual|
|
25
|
+
unless @type === actual
|
26
|
+
context.add_child(label: "[]", expected: @type, actual:)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
20
30
|
end
|
@@ -39,6 +39,13 @@ class Literal::Types::UnionType
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
def record_literal_type_errors(ctx)
|
43
|
+
@types.each do |type|
|
44
|
+
ctx.add_child(label: type.inspect, expected: type, actual: ctx.actual)
|
45
|
+
end
|
46
|
+
ctx.children.clear if ctx.children.none? { |c| c.children.any? }
|
47
|
+
end
|
48
|
+
|
42
49
|
protected
|
43
50
|
|
44
51
|
attr_reader :types
|
data/lib/literal/types.rb
CHANGED
@@ -32,15 +32,15 @@ module Literal::Types
|
|
32
32
|
autoload :UnionType, "literal/types/union_type"
|
33
33
|
autoload :VoidType, "literal/types/void_type"
|
34
34
|
|
35
|
-
NilableBooleanType = NilableType.new(BooleanType)
|
36
|
-
NilableCallableType = NilableType.new(CallableType)
|
37
|
-
NilableJSONDataType = NilableType.new(JSONDataType)
|
38
|
-
NilableLambdaType = NilableType.new(LambdaType)
|
39
|
-
NilableProcableType = NilableType.new(ProcableType)
|
35
|
+
NilableBooleanType = NilableType.new(BooleanType::Instance).freeze
|
36
|
+
NilableCallableType = NilableType.new(CallableType::Instance).freeze
|
37
|
+
NilableJSONDataType = NilableType.new(JSONDataType).freeze
|
38
|
+
NilableLambdaType = NilableType.new(LambdaType).freeze
|
39
|
+
NilableProcableType = NilableType.new(ProcableType).freeze
|
40
40
|
|
41
41
|
# Matches any value except `nil`. Use `_Any?` or `_Void` to match any value including `nil`.
|
42
42
|
def _Any
|
43
|
-
AnyType
|
43
|
+
AnyType::Instance
|
44
44
|
end
|
45
45
|
|
46
46
|
def _Any?
|
@@ -61,7 +61,7 @@ module Literal::Types
|
|
61
61
|
|
62
62
|
# Matches if the value is `true` or `false`.
|
63
63
|
def _Boolean
|
64
|
-
BooleanType
|
64
|
+
BooleanType::Instance
|
65
65
|
end
|
66
66
|
|
67
67
|
# Nilable version of `_Boolean`
|
@@ -71,7 +71,7 @@ module Literal::Types
|
|
71
71
|
|
72
72
|
# Matches if the value responds to `#call`.
|
73
73
|
def _Callable
|
74
|
-
CallableType
|
74
|
+
CallableType::Instance
|
75
75
|
end
|
76
76
|
|
77
77
|
# Nilabl version of `_Callable`
|
@@ -131,7 +131,7 @@ module Literal::Types
|
|
131
131
|
|
132
132
|
# Matches *"falsy"* values (`nil` and `false`).
|
133
133
|
def _Falsy
|
134
|
-
FalsyType
|
134
|
+
FalsyType::Instance
|
135
135
|
end
|
136
136
|
|
137
137
|
# Matches if the value is a `Float` and matches the given constraint.
|
@@ -166,9 +166,9 @@ module Literal::Types
|
|
166
166
|
end
|
167
167
|
|
168
168
|
# Nilable version of `_Hash`
|
169
|
-
def _Hash?
|
169
|
+
def _Hash?(...)
|
170
170
|
NilableType.new(
|
171
|
-
HashType.new,
|
171
|
+
HashType.new(...),
|
172
172
|
)
|
173
173
|
end
|
174
174
|
|
@@ -184,7 +184,7 @@ module Literal::Types
|
|
184
184
|
# Nilable version of `_Integer`
|
185
185
|
def _Integer?(...)
|
186
186
|
NilableType.new(
|
187
|
-
IntegerType(...),
|
187
|
+
IntegerType.new(...),
|
188
188
|
)
|
189
189
|
end
|
190
190
|
|
data/lib/literal/version.rb
CHANGED