literal 1.2.0 → 1.4.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/literal/array.rb +183 -1
  3. data/lib/literal/data.rb +1 -7
  4. data/lib/literal/deferred_type.rb +23 -0
  5. data/lib/literal/enum.rb +9 -2
  6. data/lib/literal/properties/schema.rb +1 -1
  7. data/lib/literal/property.rb +4 -4
  8. data/lib/literal/transforms.rb +142 -0
  9. data/lib/literal/tuple.rb +60 -0
  10. data/lib/literal/types/any_type.rb +2 -2
  11. data/lib/literal/types/boolean_type.rb +2 -2
  12. data/lib/literal/types/constraint_type.rb +4 -2
  13. data/lib/literal/types/falsy_type.rb +2 -2
  14. data/lib/literal/types/frozen_type.rb +35 -1
  15. data/lib/literal/types/hash_type.rb +22 -1
  16. data/lib/literal/types/interface_type.rb +29 -1
  17. data/lib/literal/types/intersection_type.rb +25 -0
  18. data/lib/literal/types/json_data_type.rb +34 -4
  19. data/lib/literal/types/map_type.rb +18 -1
  20. data/lib/literal/types/never_type.rb +18 -3
  21. data/lib/literal/types/nilable_type.rb +18 -1
  22. data/lib/literal/types/not_type.rb +22 -1
  23. data/lib/literal/types/range_type.rb +18 -1
  24. data/lib/literal/types/set_type.rb +18 -1
  25. data/lib/literal/types/truthy_type.rb +17 -3
  26. data/lib/literal/types/tuple_type.rb +19 -2
  27. data/lib/literal/types/union_type.rb +21 -4
  28. data/lib/literal/types/void_type.rb +13 -3
  29. data/lib/literal/types.rb +51 -44
  30. data/lib/literal/version.rb +1 -1
  31. data/lib/literal.rb +11 -65
  32. metadata +7 -12
  33. data/lib/literal/data_property.rb +0 -16
  34. data/lib/literal/types/callable_type.rb +0 -31
  35. data/lib/literal/types/float_type.rb +0 -10
  36. data/lib/literal/types/integer_type.rb +0 -10
  37. data/lib/literal/types/lambda_type.rb +0 -12
  38. data/lib/literal/types/procable_type.rb +0 -12
  39. data/lib/literal/types/string_type.rb +0 -10
  40. data/lib/literal/types/symbol_type.rb +0 -10
@@ -2,13 +2,47 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::FrozenType
5
+ ALWAYS_FROZEN = Set[Symbol, Integer, Float, Numeric, true, false, nil].freeze
6
+
7
+ include Literal::Type
8
+
5
9
  def initialize(type)
10
+ if ALWAYS_FROZEN.include?(type)
11
+ warn "_Frozen type is redundant for #{type.inspect} since it is always frozen."
12
+ end
13
+
6
14
  @type = type
7
15
  end
8
16
 
9
- def inspect = "_Frozen(#{@type.inspect})"
17
+ attr_reader :type
18
+
19
+ def inspect
20
+ "_Frozen(#{@type.inspect})"
21
+ end
10
22
 
11
23
  def ===(value)
12
24
  value.frozen? && @type === value
13
25
  end
26
+
27
+ def >=(other)
28
+ case other
29
+ when Literal::Types::FrozenType
30
+ @type >= other.type
31
+ when Literal::Types::ConstraintType
32
+ type_match = false
33
+ frozen_match = Literal.subtype?(other.property_constraints[:frozen?], of: true)
34
+
35
+ other.object_constraints.each do |constraint|
36
+ frozen_match ||= ALWAYS_FROZEN.include?(constraint)
37
+ type_match ||= Literal.subtype?(constraint, of: @type)
38
+ return true if frozen_match && type_match
39
+ end
40
+
41
+ false
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ freeze
14
48
  end
@@ -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,6 +2,15 @@
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
@@ -9,9 +18,28 @@ class Literal::Types::InterfaceType
9
18
 
10
19
  attr_reader :methods
11
20
 
12
- def inspect = "_Interface(#{@methods.map(&:inspect).join(', ')})"
21
+ def inspect
22
+ "_Interface(#{@methods.map(&:inspect).join(', ')})"
23
+ end
13
24
 
14
25
  def ===(value)
15
26
  @methods.all? { |m| value.respond_to?(m) }
16
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
17
45
  end
@@ -2,6 +2,8 @@
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
 
@@ -27,4 +29,27 @@ class Literal::Types::IntersectionType
27
29
  context.add_child(label: inspect, expected: type, actual: context.actual)
28
30
  end
29
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
30
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,7 +34,7 @@ module Literal::Types::JSONDataType
19
34
  end
20
35
  end
21
36
 
22
- def self.record_literal_type_errors(context)
37
+ def record_literal_type_errors(context)
23
38
  case value = context.actual
24
39
  when String, Integer, Float, true, false, nil
25
40
  # nothing to do
@@ -35,5 +50,20 @@ module Literal::Types::JSONDataType
35
50
  end
36
51
  end
37
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
+
38
68
  freeze
39
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
@@ -23,7 +27,7 @@ class Literal::Types::MapType
23
27
 
24
28
  @shape.each do |key, expected|
25
29
  unless context.actual.key?(key) || expected === nil
26
- context.add_child(label: "[]", expected: key, actual: nil)
30
+ context.add_child(label: "[#{key.inspect}]", expected:, actual: nil)
27
31
  next
28
32
  end
29
33
 
@@ -33,4 +37,17 @@ class Literal::Types::MapType
33
37
  end
34
38
  end
35
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)
48
+ end
49
+ else
50
+ false
51
+ end
52
+ end
36
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,11 +2,17 @@
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
@@ -16,5 +22,16 @@ class Literal::Types::NilableType
16
22
  @type.record_literal_type_errors(ctx) if @type.respond_to?(:record_literal_type_errors)
17
23
  end
18
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
+
19
36
  freeze
20
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
@@ -27,4 +33,15 @@ class Literal::Types::SetType
27
33
  end
28
34
  end
29
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
30
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
@@ -46,9 +50,20 @@ class Literal::Types::UnionType
46
50
  ctx.children.clear if ctx.children.none? { |c| c.children.any? }
47
51
  end
48
52
 
49
- protected
50
-
51
- 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
52
67
 
53
68
  private
54
69
 
@@ -57,4 +72,6 @@ class Literal::Types::UnionType
57
72
  (Literal::Types::UnionType === type) ? load_types(type.types) : @types << type
58
73
  end
59
74
  end
75
+
76
+ freeze
60
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