literal 1.1.0 → 1.2.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.
@@ -6,4 +6,30 @@ class Literal::Struct < Literal::DataStructure
6
6
  super
7
7
  end
8
8
  end
9
+
10
+ def [](key)
11
+ case key
12
+ when Symbol
13
+ when String
14
+ key = key.intern
15
+ else
16
+ raise TypeError.new("expected a string or symbol, got #{key.inspect.class}")
17
+ end
18
+
19
+ prop = self.class.literal_properties[key] || raise(NameError.new("unknown attribute: #{key.inspect} for #{self.class}"))
20
+ __send__(prop.name)
21
+ end
22
+
23
+ def []=(key, value)
24
+ case key
25
+ when Symbol
26
+ when String
27
+ key = key.intern
28
+ else
29
+ raise TypeError.new("expected a string or symbol, got #{key.inspect.class}")
30
+ end
31
+
32
+ prop = self.class.literal_properties[key] || raise(NameError.new("unknown attribute: #{key.inspect} for #{self.class}"))
33
+ __send__(:"#{prop.name}=", value)
34
+ end
9
35
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Literal::Type
4
+ def >=(other)
5
+ self == other
6
+ end
7
+ end
@@ -1,12 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::AnyType
5
- def self.inspect = "_Any"
4
+ class Literal::Types::AnyType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def inspect
8
+ "_Any"
9
+ end
10
+
11
+ def ===(value)
8
12
  !(nil === value)
9
13
  end
10
14
 
15
+ def >=(other)
16
+ !(other === nil)
17
+ end
18
+
19
+ Instance = new.freeze
20
+
11
21
  freeze
12
22
  end
@@ -2,16 +2,31 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::ArrayType
5
+ include Literal::Type
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def inspect = "_Array(#{@type.inspect})"
11
+ attr_reader :type
12
+
13
+ def inspect
14
+ "_Array(#{@type.inspect})"
15
+ end
10
16
 
11
17
  def ===(value)
12
18
  Array === value && value.all?(@type)
13
19
  end
14
20
 
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::ArrayType
24
+ @type >= other.type
25
+ else
26
+ false
27
+ end
28
+ end
29
+
15
30
  def record_literal_type_errors(context)
16
31
  unless Array === context.actual
17
32
  return
@@ -23,4 +38,6 @@ class Literal::Types::ArrayType
23
38
  end
24
39
  end
25
40
  end
41
+
42
+ freeze
26
43
  end
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::BooleanType
5
- def self.inspect = "_Boolean"
4
+ class Literal::Types::BooleanType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def inspect
8
+ "_Boolean"
9
+ end
10
+
11
+ def ===(value)
8
12
  true == value || false == value
9
13
  end
10
14
 
15
+ def >=(other)
16
+ case other
17
+ when true, false, Literal::Types::BooleanType
18
+ true
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ Instance = new.freeze
25
+
11
26
  freeze
12
27
  end
@@ -1,12 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::CallableType
5
- def self.inspect = "_Callable"
4
+ class Literal::Types::CallableType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def inspect
8
+ "_Callable"
9
+ end
10
+
11
+ def ===(value)
8
12
  value.respond_to?(:call)
9
13
  end
10
14
 
15
+ def >=(other)
16
+ (self == other) || (Proc == other) || (Method == other) || case other
17
+ when Literal::Types::IntersectionType
18
+ other.types.any? { |type| Literal.subtype?(type, of: self) }
19
+ when Literal::Types::ConstraintType
20
+ other.object_constraints.any? { |type| Literal.subtype?(type, of: self) }
21
+ when Literal::Types::InterfaceType
22
+ other.methods.include?(:call)
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ Instance = new.freeze
29
+
11
30
  freeze
12
31
  end
@@ -2,13 +2,32 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::ClassType
5
+ include Literal::Type
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def inspect = "_Class(#{@type.name})"
11
+ attr_reader :type
12
+
13
+ def inspect
14
+ "_Class(#{@type.name})"
15
+ end
10
16
 
11
17
  def ===(value)
12
18
  Class === value && (value == @type || value < @type)
13
19
  end
20
+
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::ClassType
24
+ Literal.subtype?(other.type, of: @type)
25
+ when Literal::Types::DescendantType
26
+ (Class === other.type) && Literal.subtype?(other.type, of: @type)
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ freeze
14
33
  end
@@ -2,12 +2,19 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::ConstraintType
5
+ include Literal::Type
6
+
5
7
  def initialize(*object_constraints, **property_constraints)
6
8
  @object_constraints = object_constraints
7
9
  @property_constraints = property_constraints
8
10
  end
9
11
 
10
- def inspect = "_Constraint(#{inspect_constraints})"
12
+ attr_reader :object_constraints
13
+ attr_reader :property_constraints
14
+
15
+ def inspect
16
+ "_Constraint(#{inspect_constraints})"
17
+ end
11
18
 
12
19
  def ===(value)
13
20
  object_constraints = @object_constraints
@@ -20,11 +27,40 @@ class Literal::Types::ConstraintType
20
27
 
21
28
  @property_constraints.each do |a, t|
22
29
  return false unless t === value.public_send(a)
30
+ rescue NoMethodError => e
31
+ raise unless e.name == a && e.receiver == value
32
+ return false
23
33
  end
24
34
 
25
35
  true
26
36
  end
27
37
 
38
+ def >=(other)
39
+ case other
40
+ when Literal::Types::ConstraintType
41
+ other_object_constraints = other.object_constraints
42
+ return false unless @object_constraints.all? do |constraint|
43
+ other_object_constraints.all? { |c| Literal.subtype?(c, of: constraint) }
44
+ end
45
+
46
+ other_property_constraints = other.property_constraints
47
+ return false unless @property_constraints.all? do |k, v|
48
+ Literal.subtype?(other_property_constraints[k], of: v)
49
+ end
50
+
51
+ true
52
+ when Literal::Types::IntersectionType
53
+ other_object_constraints = other.types
54
+ return false unless @object_constraints.all? do |constraint|
55
+ other_object_constraints.all? { |c| Literal.subtype?(c, of: constraint) }
56
+ end
57
+
58
+ true
59
+ else
60
+ false
61
+ end
62
+ end
63
+
28
64
  def record_literal_type_errors(context)
29
65
  @object_constraints.each do |constraint|
30
66
  next if constraint === context.actual
@@ -33,6 +69,7 @@ class Literal::Types::ConstraintType
33
69
  end
34
70
 
35
71
  @property_constraints.each do |property, constraint|
72
+ next unless context.actual.respond_to?(property)
36
73
  actual = context.actual.public_send(property)
37
74
  next if constraint === actual
38
75
 
@@ -57,4 +94,6 @@ class Literal::Types::ConstraintType
57
94
  @property_constraints.map { |k, t| "#{k}: #{t.inspect}" }.join(", ")
58
95
  end
59
96
  end
97
+
98
+ freeze
60
99
  end
@@ -1,13 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Literal::Types::DescendantType
4
+ include Literal::Type
5
+
4
6
  def initialize(type)
5
7
  @type = type
6
8
  end
7
9
 
8
- def inspect = "_Descendant(#{@type})"
10
+ attr_reader :type
11
+
12
+ def inspect
13
+ "_Descendant(#{@type})"
14
+ end
9
15
 
10
16
  def ===(value)
11
17
  Module === value && value < @type
12
18
  end
19
+
20
+ def >=(other)
21
+ case other
22
+ when Literal::Types::DescendantType, Literal::Types::ClassType
23
+ Literal.subtype?(other.type, of: @type)
24
+ else
25
+ false
26
+ end
27
+ end
28
+
29
+ freeze
13
30
  end
@@ -2,13 +2,30 @@
2
2
 
3
3
  # @api private
4
4
  class Literal::Types::EnumerableType
5
+ include Literal::Type
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def inspect = "_Enumerable(#{@type.inspect})"
11
+ attr_reader :type
12
+
13
+ def inspect
14
+ "_Enumerable(#{@type.inspect})"
15
+ end
10
16
 
11
17
  def ===(value)
12
18
  Enumerable === value && value.all?(@type)
13
19
  end
20
+
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::EnumerableType
24
+ Literal.subtype?(other.type, of: @type)
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ freeze
14
31
  end
@@ -1,12 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- module Literal::Types::FalsyType
5
- def self.inspect = "_Falsy"
4
+ class Literal::Types::FalsyType
5
+ include Literal::Type
6
6
 
7
- def self.===(value)
7
+ def initialize
8
+ freeze
9
+ end
10
+
11
+ def inspect
12
+ "_Falsy"
13
+ end
14
+
15
+ def ===(value)
8
16
  !value
9
17
  end
10
18
 
19
+ def >=(other)
20
+ case other
21
+ when Literal::Types::FalsyType, nil, false
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ Instance = new.freeze
29
+
11
30
  freeze
12
31
  end
@@ -7,6 +7,8 @@ class Literal::Types::InterfaceType
7
7
  @methods = methods
8
8
  end
9
9
 
10
+ attr_reader :methods
11
+
10
12
  def inspect = "_Interface(#{@methods.map(&:inspect).join(', ')})"
11
13
 
12
14
  def ===(value)
@@ -8,6 +8,8 @@ class Literal::Types::IntersectionType
8
8
  @types = types
9
9
  end
10
10
 
11
+ attr_reader :types
12
+
11
13
  def inspect = "_Intersection(#{@types.map(&:inspect).join(', ')})"
12
14
 
13
15
  def ===(value)
@@ -19,5 +19,21 @@ module Literal::Types::JSONDataType
19
19
  end
20
20
  end
21
21
 
22
+ def self.record_literal_type_errors(context)
23
+ case value = context.actual
24
+ when String, Integer, Float, true, false, nil
25
+ # nothing to do
26
+ when Hash
27
+ value.each do |k, v|
28
+ context.add_child(label: "[]", expected: String, actual: k) unless String === k
29
+ context.add_child(label: "[#{k.inspect}]", expected: self, actual: v) unless self === v
30
+ end
31
+ when Array
32
+ value.each_with_index do |item, index|
33
+ context.add_child(label: "[#{index}]", expected: self, actual: item) unless self === item
34
+ end
35
+ end
36
+ end
37
+
22
38
  freeze
23
39
  end
@@ -21,14 +21,15 @@ class Literal::Types::MapType
21
21
  return
22
22
  end
23
23
 
24
- context.actual.each do |key, item|
25
- unless (expected = @shape[key])
26
- context.add_child(label: "[]", expected: @shape.keys, actual: key)
24
+ @shape.each do |key, expected|
25
+ unless context.actual.key?(key) || expected === nil
26
+ context.add_child(label: "[]", expected: key, actual: nil)
27
27
  next
28
28
  end
29
29
 
30
- unless expected === item
31
- context.add_child(label: "[#{key.inspect}]", expected:, actual: item)
30
+ actual = context.actual[key]
31
+ unless expected === actual
32
+ context.add_child(label: "[#{key.inspect}]", expected:, actual:)
32
33
  end
33
34
  end
34
35
  end
@@ -11,4 +11,10 @@ class Literal::Types::NilableType
11
11
  def ===(value)
12
12
  nil === value || @type === value
13
13
  end
14
+
15
+ def record_literal_type_errors(ctx)
16
+ @type.record_literal_type_errors(ctx) if @type.respond_to?(:record_literal_type_errors)
17
+ end
18
+
19
+ freeze
14
20
  end
@@ -17,4 +17,14 @@ class Literal::Types::SetType
17
17
 
18
18
  true
19
19
  end
20
+
21
+ def record_literal_type_errors(context)
22
+ return unless Set === context.actual
23
+
24
+ context.actual.each do |actual|
25
+ unless @type === actual
26
+ context.add_child(label: "[]", expected: @type, actual:)
27
+ end
28
+ end
29
+ end
20
30
  end
@@ -39,6 +39,13 @@ class Literal::Types::UnionType
39
39
  end
40
40
  end
41
41
 
42
+ def record_literal_type_errors(ctx)
43
+ @types.each do |type|
44
+ ctx.add_child(label: type.inspect, expected: type, actual: ctx.actual)
45
+ end
46
+ ctx.children.clear if ctx.children.none? { |c| c.children.any? }
47
+ end
48
+
42
49
  protected
43
50
 
44
51
  attr_reader :types
data/lib/literal/types.rb CHANGED
@@ -32,15 +32,15 @@ module Literal::Types
32
32
  autoload :UnionType, "literal/types/union_type"
33
33
  autoload :VoidType, "literal/types/void_type"
34
34
 
35
- NilableBooleanType = NilableType.new(BooleanType)
36
- NilableCallableType = NilableType.new(CallableType)
37
- NilableJSONDataType = NilableType.new(JSONDataType)
38
- NilableLambdaType = NilableType.new(LambdaType)
39
- NilableProcableType = NilableType.new(ProcableType)
35
+ NilableBooleanType = NilableType.new(BooleanType::Instance).freeze
36
+ NilableCallableType = NilableType.new(CallableType::Instance).freeze
37
+ NilableJSONDataType = NilableType.new(JSONDataType).freeze
38
+ NilableLambdaType = NilableType.new(LambdaType).freeze
39
+ NilableProcableType = NilableType.new(ProcableType).freeze
40
40
 
41
41
  # Matches any value except `nil`. Use `_Any?` or `_Void` to match any value including `nil`.
42
42
  def _Any
43
- AnyType
43
+ AnyType::Instance
44
44
  end
45
45
 
46
46
  def _Any?
@@ -61,7 +61,7 @@ module Literal::Types
61
61
 
62
62
  # Matches if the value is `true` or `false`.
63
63
  def _Boolean
64
- BooleanType
64
+ BooleanType::Instance
65
65
  end
66
66
 
67
67
  # Nilable version of `_Boolean`
@@ -71,7 +71,7 @@ module Literal::Types
71
71
 
72
72
  # Matches if the value responds to `#call`.
73
73
  def _Callable
74
- CallableType
74
+ CallableType::Instance
75
75
  end
76
76
 
77
77
  # Nilabl version of `_Callable`
@@ -131,7 +131,7 @@ module Literal::Types
131
131
 
132
132
  # Matches *"falsy"* values (`nil` and `false`).
133
133
  def _Falsy
134
- FalsyType
134
+ FalsyType::Instance
135
135
  end
136
136
 
137
137
  # Matches if the value is a `Float` and matches the given constraint.
@@ -166,9 +166,9 @@ module Literal::Types
166
166
  end
167
167
 
168
168
  # Nilable version of `_Hash`
169
- def _Hash?
169
+ def _Hash?(...)
170
170
  NilableType.new(
171
- HashType.new,
171
+ HashType.new(...),
172
172
  )
173
173
  end
174
174
 
@@ -184,7 +184,7 @@ module Literal::Types
184
184
  # Nilable version of `_Integer`
185
185
  def _Integer?(...)
186
186
  NilableType.new(
187
- IntegerType(...),
187
+ IntegerType.new(...),
188
188
  )
189
189
  end
190
190
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end