literal 1.1.0 → 1.3.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 (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