literal 1.1.0 → 1.3.0

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