literal 1.2.0 → 1.4.0

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