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
@@ -157,7 +157,7 @@ class Literal::Property
157
157
 
158
158
  def generate_initializer_handle_property(buffer = +"")
159
159
  buffer << " # " << @name.name << "\n" <<
160
- " property = properties[:" << @name.name << "]\n"
160
+ " property = __properties__[:" << @name.name << "]\n"
161
161
 
162
162
  if @kind == :keyword && ruby_keyword?
163
163
  generate_initializer_escape_keyword(buffer)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Rails::FlagsType < ActiveModel::Type::Value
4
+ def initialize(flags_class)
5
+ @flags_class = flags_class
6
+ super()
7
+ end
8
+
9
+ def cast(value)
10
+ case value
11
+ when @flags_class
12
+ value
13
+ else
14
+ deserialize(value)
15
+ end
16
+ end
17
+
18
+ def serialize(value)
19
+ case value
20
+ when nil
21
+ nil
22
+ when @flags_class
23
+ value.to_bit_string
24
+ else
25
+ raise Literal::ArgumentError.new(
26
+ "Invalid value: #{value.inspect}. Expected an #{@flags_class.inspect}.",
27
+ )
28
+ end
29
+ end
30
+
31
+ def deserialize(value)
32
+ case value
33
+ when nil
34
+ nil
35
+ else
36
+ @flags_class.from_bit_string(value) || raise(
37
+ ArgumentError.new("Invalid value: #{value.inspect} for #{@flags_class}"),
38
+ )
39
+ end
40
+ end
41
+ end
data/lib/literal/rails.rb CHANGED
@@ -5,5 +5,6 @@ require_relative "rails/patches/active_record"
5
5
 
6
6
  module Literal::Rails
7
7
  autoload :EnumType, "literal/rails/enum_type"
8
+ autoload :FlagsType, "literal/rails/flags_type"
8
9
  autoload :EnumSerializer, "literal/rails/enum_serializer"
9
10
  end
@@ -6,8 +6,16 @@ class Literal::Railtie < Rails::Railtie
6
6
  Literal::Rails::EnumType.new(type)
7
7
  end
8
8
 
9
+ ActiveRecord::Type.register(:literal_flags) do |name, type:|
10
+ Literal::Rails::FlagsType.new(type)
11
+ end
12
+
9
13
  ActiveModel::Type.register(:literal_enum) do |name, type:|
10
14
  Literal::Rails::EnumType.new(type)
11
15
  end
16
+
17
+ ActiveModel::Type.register(:literal_flags) do |name, type:|
18
+ Literal::Rails::FlagsType.new(type)
19
+ end
12
20
  end
13
21
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Set
4
+ class Generic
5
+ def initialize(type)
6
+ @type = type
7
+ end
8
+
9
+ def new(*value)
10
+ Literal::Set.new(value.to_set, type: @type)
11
+ end
12
+
13
+ alias_method :[], :new
14
+
15
+ def ===(value)
16
+ Literal::Set === value && @type == value.__type__
17
+ end
18
+
19
+ def inspect
20
+ "Literal::Set(#{@type.inspect})"
21
+ end
22
+ end
23
+
24
+ include Enumerable
25
+
26
+ def initialize(value, type:)
27
+ collection_type = Literal::Types::SetType.new(type)
28
+
29
+ Literal.check(actual: value, expected: collection_type) do |c|
30
+ c.fill_receiver(receiver: self, method: "#initialize")
31
+ end
32
+
33
+ @__type__ = type
34
+ @__value__ = value
35
+ @__collection_type__ = collection_type
36
+ end
37
+
38
+ attr_reader :__type__, :__value__
39
+
40
+ def freeze
41
+ @__value__.freeze
42
+ super
43
+ end
44
+
45
+ def each(...)
46
+ @__value__.each(...)
47
+ end
48
+ end
@@ -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,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A map of core types to transform Procs mapping to the new type.
4
+ Literal::TRANSFORMS = {
5
+ Integer => {
6
+ abs: Integer,
7
+ ceil: Integer,
8
+ chr: String,
9
+ denominator: Integer,
10
+ even?: Literal::Types::BooleanType::Instance,
11
+ floor: Integer,
12
+ hash: Integer,
13
+ inspect: String,
14
+ integer?: true,
15
+ magnitude: Integer,
16
+ negative?: Literal::Types::BooleanType::Instance,
17
+ next: Integer,
18
+ numerator: Integer,
19
+ odd?: Literal::Types::BooleanType::Instance,
20
+ ord: Integer,
21
+ positive?: Literal::Types::BooleanType::Instance,
22
+ pred: Integer,
23
+ round: Integer,
24
+ size: Integer,
25
+ succ: Integer,
26
+ to_f: Float,
27
+ to_i: Integer,
28
+ to_int: Integer,
29
+ to_r: Rational,
30
+ to_s: String,
31
+ truncate: Integer,
32
+ zero?: Literal::Types::BooleanType::Instance,
33
+ },
34
+ String => {
35
+ ascii_only?: Literal::Types::BooleanType::Instance,
36
+ bytesize: Integer,
37
+ capitalize: String,
38
+ chomp: String,
39
+ chop: String,
40
+ downcase: String,
41
+ dump: String,
42
+ empty?: Literal::Types::BooleanType::Instance,
43
+ hash: Integer,
44
+ inspect: String,
45
+ length: Integer,
46
+ lstrip: String,
47
+ ord: Integer,
48
+ reverse: String,
49
+ rstrip: String,
50
+ scrub: String,
51
+ size: Integer,
52
+ strip: String,
53
+ swapcase: String,
54
+ to_str: String,
55
+ upcase: String,
56
+ valid_encoding?: Literal::Types::BooleanType::Instance,
57
+ },
58
+ Numeric => {
59
+ to_i: Integer,
60
+ to_f: Float,
61
+ to_s: String,
62
+ },
63
+ Array => {
64
+ size: Integer,
65
+ length: Integer,
66
+ empty?: Literal::Types::BooleanType::Instance,
67
+ sort: Array,
68
+ to_a: Array,
69
+ to_ary: Array,
70
+ },
71
+ Hash => {
72
+ empty?: Literal::Types::BooleanType::Instance,
73
+ inspect: String,
74
+ keys: Array,
75
+ length: Integer,
76
+ size: Integer,
77
+ to_a: Array,
78
+ to_h: Hash,
79
+ to_s: String,
80
+ values: Array,
81
+ },
82
+ Set => {
83
+ empty?: Literal::Types::BooleanType::Instance,
84
+ inspect: String,
85
+ length: Integer,
86
+ size: Integer,
87
+ to_a: Array,
88
+ to_s: String,
89
+ },
90
+ Float => {
91
+ abs: Float,
92
+ ceil: Integer,
93
+ floor: Integer,
94
+ nan?: Literal::Types::BooleanType::Instance,
95
+ negative?: Literal::Types::BooleanType::Instance,
96
+ positive?: Literal::Types::BooleanType::Instance,
97
+ round: Integer,
98
+ to_i: Integer,
99
+ to_s: String,
100
+ truncate: Integer,
101
+ zero?: Literal::Types::BooleanType::Instance,
102
+ },
103
+ Symbol => {
104
+ empty?: Literal::Types::BooleanType::Instance,
105
+ inspect: String,
106
+ length: Integer,
107
+ size: Integer,
108
+ to_s: String,
109
+ to_sym: Symbol,
110
+ },
111
+ Range => {
112
+ begin: Object,
113
+ end: Object,
114
+ exclude_end?: Literal::Types::BooleanType::Instance,
115
+ first: Object,
116
+ last: Object,
117
+ max: Object,
118
+ min: Object,
119
+ size: Integer,
120
+ to_a: Array,
121
+ to_s: String,
122
+ },
123
+ Regexp => {
124
+ casefold?: Literal::Types::BooleanType::Instance,
125
+ inspect: String,
126
+ source: String,
127
+ to_s: String,
128
+ },
129
+ Time => {
130
+ day: Integer,
131
+ hour: Integer,
132
+ inspect: String,
133
+ min: Integer,
134
+ month: Integer,
135
+ sec: Integer,
136
+ to_a: Array,
137
+ to_f: Float,
138
+ to_i: Integer,
139
+ to_s: String,
140
+ year: Integer,
141
+ },
142
+ }.transform_values! { |it| it.transform_keys(&:to_proc) }.freeze
@@ -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
+ Instance = new.freeze
6
6
 
7
- def self.===(value)
7
+ include Literal::Type
8
+
9
+ def inspect
10
+ "_Any"
11
+ end
12
+
13
+ def ===(value)
8
14
  !(nil === value)
9
15
  end
10
16
 
17
+ def >=(other)
18
+ !(other === nil)
19
+ end
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
+ Instance = new.freeze
6
6
 
7
- def self.===(value)
7
+ include Literal::Type
8
+
9
+ def inspect
10
+ "_Boolean"
11
+ end
12
+
13
+ def ===(value)
8
14
  true == value || false == value
9
15
  end
10
16
 
17
+ def >=(other)
18
+ case other
19
+ when true, false, Literal::Types::BooleanType
20
+ true
21
+ else
22
+ false
23
+ end
24
+ end
25
+
11
26
  freeze
12
27
  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,42 @@ 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.any? { |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.any? { |c| Literal.subtype?(c, of: constraint) }
56
+ end
57
+
58
+ true
59
+ when Literal::Types::FrozenType
60
+ @object_constraints.all? { |constraint| Literal.subtype?(other.type, of: constraint) }
61
+ else
62
+ false
63
+ end
64
+ end
65
+
28
66
  def record_literal_type_errors(context)
29
67
  @object_constraints.each do |constraint|
30
68
  next if constraint === context.actual
@@ -33,6 +71,7 @@ class Literal::Types::ConstraintType
33
71
  end
34
72
 
35
73
  @property_constraints.each do |property, constraint|
74
+ next unless context.actual.respond_to?(property)
36
75
  actual = context.actual.public_send(property)
37
76
  next if constraint === actual
38
77
 
@@ -57,4 +96,6 @@ class Literal::Types::ConstraintType
57
96
  @property_constraints.map { |k, t| "#{k}: #{t.inspect}" }.join(", ")
58
97
  end
59
98
  end
99
+
100
+ freeze
60
101
  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
+ Instance = new.freeze
6
6
 
7
- def self.===(value)
7
+ include Literal::Type
8
+
9
+ def initialize
10
+ freeze
11
+ end
12
+
13
+ def inspect
14
+ "_Falsy"
15
+ end
16
+
17
+ def ===(value)
8
18
  !value
9
19
  end
10
20
 
21
+ def >=(other)
22
+ case other
23
+ when Literal::Types::FalsyType, nil, false
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
11
30
  freeze
12
31
  end
@@ -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