literal 1.1.0 → 1.3.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 +544 -0
- data/lib/literal/data_structure.rb +0 -10
- data/lib/literal/deferred_type.rb +23 -0
- data/lib/literal/flags.rb +17 -4
- data/lib/literal/hash.rb +48 -0
- data/lib/literal/properties/schema.rb +2 -2
- data/lib/literal/properties.rb +5 -0
- data/lib/literal/property.rb +1 -1
- 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/transforms.rb +142 -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/class_type.rb +20 -1
- data/lib/literal/types/constraint_type.rb +42 -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/frozen_type.rb +35 -1
- data/lib/literal/types/hash_type.rb +22 -1
- data/lib/literal/types/interface_type.rb +31 -1
- data/lib/literal/types/intersection_type.rb +27 -0
- data/lib/literal/types/json_data_type.rb +49 -3
- data/lib/literal/types/map_type.rb +23 -5
- data/lib/literal/types/never_type.rb +18 -3
- data/lib/literal/types/nilable_type.rb +24 -1
- data/lib/literal/types/not_type.rb +22 -1
- data/lib/literal/types/range_type.rb +18 -1
- data/lib/literal/types/set_type.rb +28 -1
- data/lib/literal/types/truthy_type.rb +17 -3
- data/lib/literal/types/tuple_type.rb +19 -2
- data/lib/literal/types/union_type.rb +27 -3
- data/lib/literal/types/void_type.rb +13 -3
- data/lib/literal/types.rb +58 -51
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +38 -5
- metadata +9 -9
- data/lib/literal/types/callable_type.rb +0 -12
- data/lib/literal/types/float_type.rb +0 -10
- data/lib/literal/types/integer_type.rb +0 -10
- data/lib/literal/types/lambda_type.rb +0 -12
- data/lib/literal/types/procable_type.rb +0 -12
- data/lib/literal/types/string_type.rb +0 -10
- data/lib/literal/types/symbol_type.rb +0 -10
@@ -2,12 +2,18 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::HashType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(key_type, value_type)
|
6
8
|
@key_type = key_type
|
7
9
|
@value_type = value_type
|
8
10
|
end
|
9
11
|
|
10
|
-
|
12
|
+
attr_reader :key_type, :value_type
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"_Hash(#{@key_type.inspect}, #{@value_type.inspect})"
|
16
|
+
end
|
11
17
|
|
12
18
|
def ===(value)
|
13
19
|
return false unless Hash === value
|
@@ -19,6 +25,19 @@ class Literal::Types::HashType
|
|
19
25
|
true
|
20
26
|
end
|
21
27
|
|
28
|
+
def >=(other)
|
29
|
+
case other
|
30
|
+
when Literal::Types::HashType
|
31
|
+
(
|
32
|
+
Literal.subtype?(other.key_type, of: @key_type)
|
33
|
+
) && (
|
34
|
+
Literal.subtype?(other.value_type, of: @value_type)
|
35
|
+
)
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
22
41
|
def record_literal_type_errors(context)
|
23
42
|
unless Hash === context.actual
|
24
43
|
return
|
@@ -35,4 +54,6 @@ class Literal::Types::HashType
|
|
35
54
|
end
|
36
55
|
end
|
37
56
|
end
|
57
|
+
|
58
|
+
freeze
|
38
59
|
end
|
@@ -2,14 +2,44 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::InterfaceType
|
5
|
+
# TODO: We can generate this and make it much more extensive.
|
6
|
+
METHOD_TYPE_MAPPINGS = {
|
7
|
+
:call => Set[Proc, Method],
|
8
|
+
:to_proc => Set[Proc, Method],
|
9
|
+
:to_s => Set[String],
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
include Literal::Type
|
13
|
+
|
5
14
|
def initialize(*methods)
|
6
15
|
raise Literal::ArgumentError.new("_Interface type must have at least one method.") if methods.size < 1
|
7
16
|
@methods = methods
|
8
17
|
end
|
9
18
|
|
10
|
-
|
19
|
+
attr_reader :methods
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"_Interface(#{@methods.map(&:inspect).join(', ')})"
|
23
|
+
end
|
11
24
|
|
12
25
|
def ===(value)
|
13
26
|
@methods.all? { |m| value.respond_to?(m) }
|
14
27
|
end
|
28
|
+
|
29
|
+
def >=(other)
|
30
|
+
case other
|
31
|
+
when Literal::Types::InterfaceType
|
32
|
+
@methods.all? { |m| other.methods.include?(m) }
|
33
|
+
when Module
|
34
|
+
@methods.map { |m| METHOD_TYPE_MAPPINGS[m] }.all? { |types| types&.include?(other) }
|
35
|
+
when Literal::Types::IntersectionType
|
36
|
+
other.types.any? { |type| Literal.subtype?(type, of: self) }
|
37
|
+
when Literal::Types::ConstraintType
|
38
|
+
other.object_constraints.any? { |type| Literal.subtype?(type, of: self) }
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
freeze
|
15
45
|
end
|
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::IntersectionType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(*types)
|
6
8
|
raise Literal::ArgumentError.new("_Intersection type must have at least one type.") if types.size < 1
|
7
9
|
|
8
10
|
@types = types
|
9
11
|
end
|
10
12
|
|
13
|
+
attr_reader :types
|
14
|
+
|
11
15
|
def inspect = "_Intersection(#{@types.map(&:inspect).join(', ')})"
|
12
16
|
|
13
17
|
def ===(value)
|
@@ -25,4 +29,27 @@ class Literal::Types::IntersectionType
|
|
25
29
|
context.add_child(label: inspect, expected: type, actual: context.actual)
|
26
30
|
end
|
27
31
|
end
|
32
|
+
|
33
|
+
def >=(other)
|
34
|
+
case other
|
35
|
+
when Literal::Types::IntersectionType
|
36
|
+
@types.all? do |type|
|
37
|
+
other.types.any? do |other_type|
|
38
|
+
Literal.subtype?(other_type, of: type)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
when Literal::Types::ConstraintType
|
42
|
+
@types.all? do |type|
|
43
|
+
other.object_constraints.any? do |object_constraint|
|
44
|
+
Literal.subtype?(object_constraint, of: type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
when Literal::Types::FrozenType
|
48
|
+
@types.all? { |type| Literal.subtype?(other.type, of: type) }
|
49
|
+
else
|
50
|
+
false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
freeze
|
28
55
|
end
|
@@ -1,10 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::JSONDataType
|
5
|
+
Instance = new.freeze
|
6
6
|
|
7
|
-
|
7
|
+
include Literal::Type
|
8
|
+
|
9
|
+
COMPATIBLE_TYPES = Set[
|
10
|
+
Integer,
|
11
|
+
Float,
|
12
|
+
String,
|
13
|
+
true,
|
14
|
+
false,
|
15
|
+
nil,
|
16
|
+
Literal::Types::BooleanType::Instance,
|
17
|
+
Instance
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
def inspect = "_JSONData"
|
21
|
+
|
22
|
+
def ===(value)
|
8
23
|
case value
|
9
24
|
when String, Integer, Float, true, false, nil
|
10
25
|
true
|
@@ -19,5 +34,36 @@ module Literal::Types::JSONDataType
|
|
19
34
|
end
|
20
35
|
end
|
21
36
|
|
37
|
+
def record_literal_type_errors(context)
|
38
|
+
case value = context.actual
|
39
|
+
when String, Integer, Float, true, false, nil
|
40
|
+
# nothing to do
|
41
|
+
when Hash
|
42
|
+
value.each do |k, v|
|
43
|
+
context.add_child(label: "[]", expected: String, actual: k) unless String === k
|
44
|
+
context.add_child(label: "[#{k.inspect}]", expected: self, actual: v) unless self === v
|
45
|
+
end
|
46
|
+
when Array
|
47
|
+
value.each_with_index do |item, index|
|
48
|
+
context.add_child(label: "[#{index}]", expected: self, actual: item) unless self === item
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def >=(other)
|
54
|
+
return true if COMPATIBLE_TYPES.include?(other)
|
55
|
+
|
56
|
+
case other
|
57
|
+
when Literal::Types::ArrayType
|
58
|
+
self >= other.type
|
59
|
+
when Literal::Types::HashType
|
60
|
+
(self >= other.key_type) && (self >= other.value_type)
|
61
|
+
when Literal::Types::ConstraintType
|
62
|
+
other.object_constraints.any? { |type| self >= type }
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
22
68
|
freeze
|
23
69
|
end
|
@@ -2,10 +2,14 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::MapType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(**shape)
|
6
8
|
@shape = shape
|
7
9
|
end
|
8
10
|
|
11
|
+
attr_reader :shape
|
12
|
+
|
9
13
|
def inspect
|
10
14
|
"_Map(#{@shape.inspect})"
|
11
15
|
end
|
@@ -21,15 +25,29 @@ class Literal::Types::MapType
|
|
21
25
|
return
|
22
26
|
end
|
23
27
|
|
24
|
-
|
25
|
-
unless (expected
|
26
|
-
context.add_child(label: "[]", expected
|
28
|
+
@shape.each do |key, expected|
|
29
|
+
unless context.actual.key?(key) || expected === nil
|
30
|
+
context.add_child(label: "[#{key.inspect}]", expected:, actual: nil)
|
27
31
|
next
|
28
32
|
end
|
29
33
|
|
30
|
-
|
31
|
-
|
34
|
+
actual = context.actual[key]
|
35
|
+
unless expected === actual
|
36
|
+
context.add_child(label: "[#{key.inspect}]", expected:, actual:)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def >=(other)
|
42
|
+
case other
|
43
|
+
when Literal::Types::MapType
|
44
|
+
other_shape = other.shape
|
45
|
+
|
46
|
+
@shape.all? do |k, v|
|
47
|
+
Literal.subtype?(other_shape[k], of: v)
|
32
48
|
end
|
49
|
+
else
|
50
|
+
false
|
33
51
|
end
|
34
52
|
end
|
35
53
|
end
|
@@ -1,12 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::NeverType
|
5
|
+
Instance = new.freeze
|
6
6
|
|
7
|
-
|
7
|
+
include Literal::Type
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
"_Never"
|
11
|
+
end
|
12
|
+
|
13
|
+
def ===(value)
|
8
14
|
false
|
9
15
|
end
|
10
16
|
|
17
|
+
def >=(other)
|
18
|
+
case other
|
19
|
+
when Literal::Types::NeverTypeClass
|
20
|
+
true
|
21
|
+
else
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
11
26
|
freeze
|
12
27
|
end
|
@@ -2,13 +2,36 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::NilableType
|
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
|
+
"_Nilable(#{@type.inspect})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
nil === value || @type === value
|
13
19
|
end
|
20
|
+
|
21
|
+
def record_literal_type_errors(ctx)
|
22
|
+
@type.record_literal_type_errors(ctx) if @type.respond_to?(:record_literal_type_errors)
|
23
|
+
end
|
24
|
+
|
25
|
+
def >=(other)
|
26
|
+
case other
|
27
|
+
when Literal::Types::NilableType
|
28
|
+
Literal.subtype?(other.type, of: @type)
|
29
|
+
when nil
|
30
|
+
true
|
31
|
+
else
|
32
|
+
Literal.subtype?(other, of: @type)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
freeze
|
14
37
|
end
|
@@ -2,13 +2,34 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::NotType
|
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
|
+
"_Not(#{@type.inspect})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
!(@type === value)
|
13
19
|
end
|
20
|
+
|
21
|
+
def >=(other)
|
22
|
+
case other
|
23
|
+
when Literal::Types::NotType
|
24
|
+
Literal.subtype?(other.type, of: @type)
|
25
|
+
when Literal::Types::ConstraintType
|
26
|
+
other.object_constraints.any? { |constraint| Literal.subtype?(constraint, of: self) }
|
27
|
+
when Literal::Types::IntersectionType
|
28
|
+
other.types.any? { |type| Literal.subtype?(type, of: self) }
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
freeze
|
14
35
|
end
|
@@ -2,11 +2,17 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::RangeType
|
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
|
+
"_Range(#{@type.inspect})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
Range === value && (
|
@@ -17,4 +23,15 @@ class Literal::Types::RangeType
|
|
17
23
|
)
|
18
24
|
)
|
19
25
|
end
|
26
|
+
|
27
|
+
def >=(other)
|
28
|
+
case other
|
29
|
+
when Literal::Types::RangeType
|
30
|
+
Literal.subtype?(other.type, of: @type)
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
freeze
|
20
37
|
end
|
@@ -2,11 +2,17 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::SetType
|
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
|
+
"_Set(#{@type.inspect})"
|
15
|
+
end
|
10
16
|
|
11
17
|
def ===(value)
|
12
18
|
return false unless Set === value
|
@@ -17,4 +23,25 @@ class Literal::Types::SetType
|
|
17
23
|
|
18
24
|
true
|
19
25
|
end
|
26
|
+
|
27
|
+
def record_literal_type_errors(context)
|
28
|
+
return unless Set === context.actual
|
29
|
+
|
30
|
+
context.actual.each do |actual|
|
31
|
+
unless @type === actual
|
32
|
+
context.add_child(label: "[]", expected: @type, actual:)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def >=(other)
|
38
|
+
case other
|
39
|
+
when Literal::Types::SetType
|
40
|
+
Literal.subtype?(other.type, of: @type)
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
freeze
|
20
47
|
end
|
@@ -1,12 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::TruthyType
|
5
|
+
Instance = new.freeze
|
6
|
+
include Literal::Type
|
6
7
|
|
7
|
-
def
|
8
|
+
def inspect
|
9
|
+
"_Truthy"
|
10
|
+
end
|
11
|
+
|
12
|
+
def ===(value)
|
8
13
|
!!value
|
9
14
|
end
|
10
15
|
|
16
|
+
def >=(other)
|
17
|
+
case other
|
18
|
+
when Literal::Types::TruthyType, true
|
19
|
+
true
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
11
25
|
freeze
|
12
26
|
end
|
@@ -2,13 +2,19 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Literal::Types::TupleType
|
5
|
+
include Literal::Type
|
6
|
+
|
5
7
|
def initialize(*types)
|
6
8
|
raise Literal::ArgumentError.new("_Tuple type must have at least one type.") if types.size < 1
|
7
9
|
|
8
10
|
@types = types
|
9
11
|
end
|
10
12
|
|
11
|
-
|
13
|
+
attr_reader :types
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"_Tuple(#{@types.map(&:inspect).join(', ')})"
|
17
|
+
end
|
12
18
|
|
13
19
|
def ===(value)
|
14
20
|
return false unless Array === value
|
@@ -32,11 +38,22 @@ class Literal::Types::TupleType
|
|
32
38
|
while i < len
|
33
39
|
actual = context.actual[i]
|
34
40
|
if !(expected = @types[i])
|
35
|
-
context.add_child(label: "[#{i}]", expected: Literal::Types::NeverType, actual:)
|
41
|
+
context.add_child(label: "[#{i}]", expected: Literal::Types::NeverType::Instance, actual:)
|
36
42
|
elsif !(expected === actual)
|
37
43
|
context.add_child(label: "[#{i}]", expected:, actual:)
|
38
44
|
end
|
39
45
|
i += 1
|
40
46
|
end
|
41
47
|
end
|
48
|
+
|
49
|
+
def >=(other)
|
50
|
+
case other
|
51
|
+
when Literal::Types::TupleType
|
52
|
+
@types == other.types
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
freeze
|
42
59
|
end
|
@@ -12,7 +12,11 @@ class Literal::Types::UnionType
|
|
12
12
|
@types.freeze
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :types
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"_Union(#{@types.inspect})"
|
19
|
+
end
|
16
20
|
|
17
21
|
def ===(value)
|
18
22
|
types = @types
|
@@ -39,9 +43,27 @@ class Literal::Types::UnionType
|
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
42
|
-
|
46
|
+
def record_literal_type_errors(ctx)
|
47
|
+
@types.each do |type|
|
48
|
+
ctx.add_child(label: type.inspect, expected: type, actual: ctx.actual)
|
49
|
+
end
|
50
|
+
ctx.children.clear if ctx.children.none? { |c| c.children.any? }
|
51
|
+
end
|
43
52
|
|
44
|
-
|
53
|
+
def >=(other)
|
54
|
+
case other
|
55
|
+
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)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
45
67
|
|
46
68
|
private
|
47
69
|
|
@@ -50,4 +72,6 @@ class Literal::Types::UnionType
|
|
50
72
|
(Literal::Types::UnionType === type) ? load_types(type.types) : @types << type
|
51
73
|
end
|
52
74
|
end
|
75
|
+
|
76
|
+
freeze
|
53
77
|
end
|
@@ -1,10 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# @api private
|
4
|
-
|
5
|
-
|
4
|
+
class Literal::Types::VoidType
|
5
|
+
Instance = new.freeze
|
6
6
|
|
7
|
-
|
7
|
+
include Literal::Type
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
"_Void"
|
11
|
+
end
|
12
|
+
|
13
|
+
def ===(_)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def >=(_)
|
8
18
|
true
|
9
19
|
end
|
10
20
|
|