literal 1.1.0 → 1.3.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 +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
|
|