literal 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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