literal 1.1.0 → 1.2.0

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