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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/literal/array.rb +544 -0
  3. data/lib/literal/data_structure.rb +0 -10
  4. data/lib/literal/deferred_type.rb +23 -0
  5. data/lib/literal/flags.rb +17 -4
  6. data/lib/literal/hash.rb +48 -0
  7. data/lib/literal/properties/schema.rb +2 -2
  8. data/lib/literal/properties.rb +5 -0
  9. data/lib/literal/property.rb +1 -1
  10. data/lib/literal/rails/flags_type.rb +41 -0
  11. data/lib/literal/rails.rb +1 -0
  12. data/lib/literal/railtie.rb +8 -0
  13. data/lib/literal/set.rb +48 -0
  14. data/lib/literal/struct.rb +26 -0
  15. data/lib/literal/transforms.rb +142 -0
  16. data/lib/literal/type.rb +7 -0
  17. data/lib/literal/types/any_type.rb +13 -3
  18. data/lib/literal/types/array_type.rb +18 -1
  19. data/lib/literal/types/boolean_type.rb +18 -3
  20. data/lib/literal/types/class_type.rb +20 -1
  21. data/lib/literal/types/constraint_type.rb +42 -1
  22. data/lib/literal/types/descendant_type.rb +18 -1
  23. data/lib/literal/types/enumerable_type.rb +18 -1
  24. data/lib/literal/types/falsy_type.rb +22 -3
  25. data/lib/literal/types/frozen_type.rb +35 -1
  26. data/lib/literal/types/hash_type.rb +22 -1
  27. data/lib/literal/types/interface_type.rb +31 -1
  28. data/lib/literal/types/intersection_type.rb +27 -0
  29. data/lib/literal/types/json_data_type.rb +49 -3
  30. data/lib/literal/types/map_type.rb +23 -5
  31. data/lib/literal/types/never_type.rb +18 -3
  32. data/lib/literal/types/nilable_type.rb +24 -1
  33. data/lib/literal/types/not_type.rb +22 -1
  34. data/lib/literal/types/range_type.rb +18 -1
  35. data/lib/literal/types/set_type.rb +28 -1
  36. data/lib/literal/types/truthy_type.rb +17 -3
  37. data/lib/literal/types/tuple_type.rb +19 -2
  38. data/lib/literal/types/union_type.rb +27 -3
  39. data/lib/literal/types/void_type.rb +13 -3
  40. data/lib/literal/types.rb +58 -51
  41. data/lib/literal/version.rb +1 -1
  42. data/lib/literal.rb +38 -5
  43. metadata +9 -9
  44. data/lib/literal/types/callable_type.rb +0 -12
  45. data/lib/literal/types/float_type.rb +0 -10
  46. data/lib/literal/types/integer_type.rb +0 -10
  47. data/lib/literal/types/lambda_type.rb +0 -12
  48. data/lib/literal/types/procable_type.rb +0 -12
  49. data/lib/literal/types/string_type.rb +0 -10
  50. 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
- def inspect = "_Hash(#{@key_type.inspect}, #{@value_type.inspect})"
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
- def inspect = "_Interface(#{@methods.map(&:inspect).join(', ')})"
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
- module Literal::Types::JSONDataType
5
- def self.inspect = "_JSONData"
4
+ class Literal::Types::JSONDataType
5
+ Instance = new.freeze
6
6
 
7
- def self.===(value)
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
- context.actual.each do |key, item|
25
- unless (expected = @shape[key])
26
- context.add_child(label: "[]", expected: @shape.keys, actual: key)
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
- unless expected === item
31
- context.add_child(label: "[#{key.inspect}]", expected:, actual: item)
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
- module Literal::Types::NeverType
5
- def self.inspect = "_Never"
4
+ class Literal::Types::NeverType
5
+ Instance = new.freeze
6
6
 
7
- def self.===(value)
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
- def inspect = "_Nilable(#{@type.inspect})"
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
- def inspect = "_Not(#{@type.inspect})"
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
- def inspect = "_Range(#{@type.inspect})"
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
- def inspect = "_Set(#{@type.inspect})"
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
- module Literal::Types::TruthyType
5
- def self.inspect = "_Truthy"
4
+ class Literal::Types::TruthyType
5
+ Instance = new.freeze
6
+ include Literal::Type
6
7
 
7
- def self.===(value)
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
- def inspect = "_Tuple(#{@types.map(&:inspect).join(', ')})"
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
- def inspect = "_Union(#{@types.inspect})"
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
- protected
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
- attr_reader :types
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
- module Literal::Types::VoidType
5
- def self.inspect = "_Void"
4
+ class Literal::Types::VoidType
5
+ Instance = new.freeze
6
6
 
7
- def self.===(_)
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