literal 1.8.1 → 1.9.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.
- checksums.yaml +4 -4
- data/lib/literal/array.rb +2 -2
- data/lib/literal/data.rb +10 -2
- data/lib/literal/data_structure.rb +2 -0
- data/lib/literal/enum.rb +17 -5
- data/lib/literal/errors/type_error.rb +13 -0
- data/lib/literal/failure.rb +79 -1
- data/lib/literal/properties/introspection.rb +59 -0
- data/lib/literal/properties/schema.rb +2 -2
- data/lib/literal/properties.rb +10 -1
- data/lib/literal/property.rb +9 -3
- data/lib/literal/rails/relation_type.rb +4 -12
- data/lib/literal/result.rb +68 -0
- data/lib/literal/result_handler.rb +54 -0
- data/lib/literal/struct.rb +1 -1
- data/lib/literal/subtype_context.rb +33 -0
- data/lib/literal/success.rb +97 -1
- data/lib/literal/tuple.rb +2 -2
- data/lib/literal/type.rb +10 -1
- data/lib/literal/types/any_type.rb +1 -1
- data/lib/literal/types/array_type.rb +7 -2
- data/lib/literal/types/boolean_type.rb +1 -1
- data/lib/literal/types/class_type.rb +3 -3
- data/lib/literal/types/constraint_type.rb +12 -5
- data/lib/literal/types/deferred_type.rb +2 -2
- data/lib/literal/types/descendant_type.rb +2 -2
- data/lib/literal/types/enumerable_type.rb +2 -2
- data/lib/literal/types/falsy_type.rb +1 -1
- data/lib/literal/types/frozen_type.rb +4 -4
- data/lib/literal/types/hash_type.rb +8 -3
- data/lib/literal/types/interface_type.rb +3 -3
- data/lib/literal/types/intersection_type.rb +11 -4
- data/lib/literal/types/json_data_type.rb +4 -4
- data/lib/literal/types/kind_type.rb +18 -0
- data/lib/literal/types/map_type.rb +2 -2
- data/lib/literal/types/never_type.rb +6 -2
- data/lib/literal/types/nilable_type.rb +3 -3
- data/lib/literal/types/not_type.rb +4 -4
- data/lib/literal/types/range_type.rb +2 -2
- data/lib/literal/types/{unit_type.rb → same_object_type.rb} +3 -3
- data/lib/literal/types/set_type.rb +2 -2
- data/lib/literal/types/tagged_union_type.rb +71 -0
- data/lib/literal/types/truthy_type.rb +1 -1
- data/lib/literal/types/tuple_type.rb +1 -1
- data/lib/literal/types/union_type.rb +44 -13
- data/lib/literal/types/void_type.rb +1 -1
- data/lib/literal/types.rb +22 -6
- data/lib/literal/undefined.rb +21 -0
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +92 -22
- data/lib/ruby_lsp/literal/addon.rb +7 -2
- metadata +9 -4
- data/lib/literal/null.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0bb6bc1253e6b8116aaf174380fb8ea5a0edbaec66d80df5f514a2a3241325b8
|
|
4
|
+
data.tar.gz: f018a260e5c17f101813d1e665f314ecebb80b9f769e59eccc8b67bb196a64b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 79d7771c617d331b8bc6a6cb94cc7949059dbcf5e449162a21c0f502de8081075aeb32810313b77d10a9439ef88c65bf4db3c1815f78eb2771466acdb9a551db
|
|
7
|
+
data.tar.gz: 8b48552cf91a9b474501f906b6bdd1b04fdfc68b0102ec388063c3d8b7dc50b5520a49bb79e00a3ef84aa56899167d416b2911f672522b60c77bde38d5cb09cc
|
data/lib/literal/array.rb
CHANGED
|
@@ -20,10 +20,10 @@ class Literal::Array
|
|
|
20
20
|
Literal::Array === value && Literal.subtype?(value.__type__, @type)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def >=(other)
|
|
23
|
+
def >=(other, context: nil)
|
|
24
24
|
case other
|
|
25
25
|
when Literal::Array::Generic
|
|
26
|
-
Literal.subtype?(other.type, @type)
|
|
26
|
+
Literal.subtype?(other.type, @type, context:)
|
|
27
27
|
else
|
|
28
28
|
false
|
|
29
29
|
end
|
data/lib/literal/data.rb
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
class Literal::Data < Literal::DataStructure
|
|
4
4
|
class << self
|
|
5
|
-
def
|
|
6
|
-
|
|
5
|
+
def [](...) = new(...)
|
|
6
|
+
|
|
7
|
+
def define(**properties)
|
|
8
|
+
Class.new(self) do
|
|
9
|
+
properties.each { |name, type| prop(name, type) }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def prop(name, type, kind = :keyword, reader: :public, predicate: false, default: nil, description: nil)
|
|
14
|
+
super(name, type, kind, reader:, writer: false, predicate:, default:, description:)
|
|
7
15
|
end
|
|
8
16
|
|
|
9
17
|
def literal_properties
|
|
@@ -29,6 +29,7 @@ class Literal::DataStructure
|
|
|
29
29
|
marshal_dump
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# required method for Marshal compatibility
|
|
32
33
|
def marshal_load(payload)
|
|
33
34
|
_version, attributes, was_frozen = payload
|
|
34
35
|
|
|
@@ -39,6 +40,7 @@ class Literal::DataStructure
|
|
|
39
40
|
freeze if was_frozen
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
# required method for Marshal compatibility
|
|
42
44
|
def marshal_dump
|
|
43
45
|
[1, to_h, frozen?].freeze
|
|
44
46
|
end
|
data/lib/literal/enum.rb
CHANGED
|
@@ -11,8 +11,8 @@ class Literal::Enum
|
|
|
11
11
|
def values = @values.keys
|
|
12
12
|
def names = @names
|
|
13
13
|
|
|
14
|
-
def prop(name, type, kind = :keyword, reader: :public, predicate: false, default: nil)
|
|
15
|
-
super(name, type, kind, reader:, writer: false, predicate:, default:)
|
|
14
|
+
def prop(name, type, kind = :keyword, reader: :public, predicate: false, default: nil, description: nil)
|
|
15
|
+
super(name, type, kind, reader:, writer: false, predicate:, default:, description:)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def inherited(subclass)
|
|
@@ -55,7 +55,7 @@ class Literal::Enum
|
|
|
55
55
|
|
|
56
56
|
types = @indexes_definitions.fetch(key)
|
|
57
57
|
type = types.first
|
|
58
|
-
Literal.check(value, type)
|
|
58
|
+
Literal.check(value, type)
|
|
59
59
|
|
|
60
60
|
@indexes.fetch(key)[value]
|
|
61
61
|
end
|
|
@@ -125,7 +125,12 @@ class Literal::Enum
|
|
|
125
125
|
|
|
126
126
|
index.each do |key, values|
|
|
127
127
|
unless type === key
|
|
128
|
-
raise Literal::TypeError.
|
|
128
|
+
raise Literal::TypeError.new(
|
|
129
|
+
context: Literal::TypeError::Context.new(
|
|
130
|
+
expected: type,
|
|
131
|
+
actual: key,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
129
134
|
end
|
|
130
135
|
|
|
131
136
|
if unique && values.size > 1
|
|
@@ -199,7 +204,14 @@ class Literal::Enum
|
|
|
199
204
|
end
|
|
200
205
|
|
|
201
206
|
alias_method :inspect, :name
|
|
202
|
-
|
|
207
|
+
|
|
208
|
+
def to_sym
|
|
209
|
+
self.class.names[self]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def to_s
|
|
213
|
+
to_sym.to_s
|
|
214
|
+
end
|
|
203
215
|
|
|
204
216
|
def deconstruct
|
|
205
217
|
[@value]
|
|
@@ -41,6 +41,19 @@ class Literal::TypeError < TypeError
|
|
|
41
41
|
expected.record_literal_type_errors(child) if expected.respond_to?(:record_literal_type_errors)
|
|
42
42
|
@children << child
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
{
|
|
47
|
+
receiver: @receiver,
|
|
48
|
+
method: @method,
|
|
49
|
+
label: @label,
|
|
50
|
+
expected: @expected,
|
|
51
|
+
actual: @actual,
|
|
52
|
+
children: @children.map(&:to_h),
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
alias to_hash to_h
|
|
44
57
|
end
|
|
45
58
|
|
|
46
59
|
def initialize(context:)
|
data/lib/literal/failure.rb
CHANGED
|
@@ -1,7 +1,85 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Literal::Failure < Literal::Result
|
|
4
|
-
|
|
4
|
+
class Generic
|
|
5
|
+
include Literal::Type
|
|
6
|
+
|
|
7
|
+
def initialize(type)
|
|
8
|
+
@type = type
|
|
9
|
+
freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :type
|
|
13
|
+
|
|
14
|
+
def ===(object)
|
|
15
|
+
Literal::Failure === object && @type === object.error!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def inspect
|
|
19
|
+
"Literal::Failure(#{@type.inspect})"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(error, success_type:, failure_type:)
|
|
5
26
|
@error = error
|
|
27
|
+
|
|
28
|
+
@success_type = success_type
|
|
29
|
+
@failure_type = failure_type
|
|
30
|
+
|
|
31
|
+
Literal.check(error, failure_type)
|
|
32
|
+
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :success_type, :failure_type
|
|
37
|
+
|
|
38
|
+
def success?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def failure?
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def value!
|
|
47
|
+
raise Literal::ArgumentError.new("Failure has no value")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def deconstruct
|
|
51
|
+
[@error]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def deconstruct_keys(keys)
|
|
55
|
+
if @error.respond_to?(:deconstruct_keys)
|
|
56
|
+
@error.deconstruct_keys(keys)
|
|
57
|
+
else
|
|
58
|
+
{}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def error!
|
|
63
|
+
@error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def map(type)
|
|
67
|
+
raise ArgumentError unless block_given?
|
|
68
|
+
|
|
69
|
+
Literal::Failure.new(
|
|
70
|
+
@error,
|
|
71
|
+
success_type: type,
|
|
72
|
+
failure_type: @failure_type
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def then
|
|
77
|
+
raise ArgumentError unless block_given?
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def value_or
|
|
82
|
+
raise ArgumentError unless block_given?
|
|
83
|
+
yield(@error)
|
|
6
84
|
end
|
|
7
85
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Literal::Properties::Introspection
|
|
4
|
+
def positional_properties
|
|
5
|
+
literal_properties.filter(&:positional?)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def keyword_properties
|
|
9
|
+
literal_properties.filter(&:keyword?)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def required_properties
|
|
13
|
+
literal_properties.filter(&:required?)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def required_positional_properties
|
|
17
|
+
required_properties.filter(&:positional?)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def required_keyword_properties
|
|
21
|
+
required_properties.filter(&:keyword?)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def optional_positional_properties
|
|
25
|
+
positional_properties - required_positional_properties
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def optional_keyword_properties
|
|
29
|
+
keyword_properties - required_keyword_properties
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def positional_property_names
|
|
33
|
+
positional_properties.map(&:name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def keyword_property_names
|
|
37
|
+
keyword_properties.map(&:name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def required_property_names
|
|
41
|
+
required_properties.map(&:name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def required_positional_property_names
|
|
45
|
+
required_positional_properties.map(&:name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def required_keyword_property_names
|
|
49
|
+
required_keyword_properties.map(&:name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def optional_positional_property_names
|
|
53
|
+
optional_positional_properties.map(&:name)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def optional_keyword_property_names
|
|
57
|
+
optional_keyword_properties.map(&:name)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -127,7 +127,7 @@ class Literal::Properties::Schema
|
|
|
127
127
|
buffer << "&" << property.escaped_name
|
|
128
128
|
when :positional
|
|
129
129
|
if property.default?
|
|
130
|
-
buffer << property.escaped_name << " = Literal::
|
|
130
|
+
buffer << property.escaped_name << " = Literal::Undefined"
|
|
131
131
|
elsif property.type === nil # optional
|
|
132
132
|
buffer << property.escaped_name << " = nil"
|
|
133
133
|
else # required
|
|
@@ -135,7 +135,7 @@ class Literal::Properties::Schema
|
|
|
135
135
|
end
|
|
136
136
|
when :keyword
|
|
137
137
|
if property.default?
|
|
138
|
-
buffer << property.name.name << ": Literal::
|
|
138
|
+
buffer << property.name.name << ": Literal::Undefined"
|
|
139
139
|
elsif property.type === nil
|
|
140
140
|
buffer << property.name.name << ": nil" # optional
|
|
141
141
|
else # required
|
data/lib/literal/properties.rb
CHANGED
|
@@ -13,7 +13,11 @@ module Literal::Properties
|
|
|
13
13
|
base.include(base.__send__(:__literal_extension__))
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def prop(name, type, kind = :keyword, reader: false, writer: false, predicate: false,
|
|
16
|
+
def prop?(name, type, kind = :keyword, reader: false, writer: false, predicate: false, &coercion)
|
|
17
|
+
prop(name, _Union(type, Literal::Undefined), kind, reader:, writer:, predicate:, default: Literal::Undefined, description: nil, &coercion)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def prop(name, type, kind = :keyword, reader: false, writer: false, predicate: false, default: nil, description: nil, &coercion)
|
|
17
21
|
if default && !(Proc === default || default.frozen?)
|
|
18
22
|
raise Literal::ArgumentError.new("The default must be a frozen object or a Proc.")
|
|
19
23
|
end
|
|
@@ -40,6 +44,10 @@ module Literal::Properties
|
|
|
40
44
|
raise Literal::ArgumentError.new("The kind must be one of #{Literal::Property::KIND_OPTIONS.map(&:inspect).join(', ')}.")
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
unless description.nil? || String === description
|
|
48
|
+
raise Literal::ArgumentError.new("The description must be a String or nil.")
|
|
49
|
+
end
|
|
50
|
+
|
|
43
51
|
property = __literal_property_class__.new(
|
|
44
52
|
name:,
|
|
45
53
|
type:,
|
|
@@ -48,6 +56,7 @@ module Literal::Properties
|
|
|
48
56
|
writer:,
|
|
49
57
|
predicate:,
|
|
50
58
|
default:,
|
|
59
|
+
description:,
|
|
51
60
|
coercion:,
|
|
52
61
|
)
|
|
53
62
|
|
data/lib/literal/property.rb
CHANGED
|
@@ -9,7 +9,7 @@ class Literal::Property
|
|
|
9
9
|
|
|
10
10
|
include Comparable
|
|
11
11
|
|
|
12
|
-
def initialize(name:, type:, kind:, reader:, writer:, predicate:, default:, coercion:)
|
|
12
|
+
def initialize(name:, type:, kind:, reader:, writer:, predicate:, default:, description:, coercion:)
|
|
13
13
|
@name = name
|
|
14
14
|
@type = type
|
|
15
15
|
@kind = kind
|
|
@@ -17,10 +17,11 @@ class Literal::Property
|
|
|
17
17
|
@writer = writer
|
|
18
18
|
@predicate = predicate
|
|
19
19
|
@default = default
|
|
20
|
+
@description = description
|
|
20
21
|
@coercion = coercion
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
attr_reader :name, :type, :kind, :reader, :writer, :predicate, :default, :coercion
|
|
24
|
+
attr_reader :name, :type, :kind, :reader, :writer, :predicate, :default, :description, :coercion
|
|
24
25
|
|
|
25
26
|
def optional?
|
|
26
27
|
default? || @type === nil
|
|
@@ -51,9 +52,14 @@ class Literal::Property
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def default?
|
|
55
|
+
return true if splat? || double_splat?
|
|
54
56
|
nil != @default
|
|
55
57
|
end
|
|
56
58
|
|
|
59
|
+
def description?
|
|
60
|
+
!!@description
|
|
61
|
+
end
|
|
62
|
+
|
|
57
63
|
def param
|
|
58
64
|
case @kind
|
|
59
65
|
when :*
|
|
@@ -183,7 +189,7 @@ class Literal::Property
|
|
|
183
189
|
private def generate_initializer_assign_default(buffer = +"")
|
|
184
190
|
buffer <<
|
|
185
191
|
" if " <<
|
|
186
|
-
((@kind == :&) ? "nil" : "Literal::
|
|
192
|
+
((@kind == :&) ? "nil" : "Literal::Undefined") <<
|
|
187
193
|
" == " <<
|
|
188
194
|
escaped_name <<
|
|
189
195
|
"\n " <<
|
|
@@ -3,14 +3,6 @@
|
|
|
3
3
|
module Literal::Rails
|
|
4
4
|
class RelationType
|
|
5
5
|
def initialize(model_class)
|
|
6
|
-
unless Class === model_class && model_class < ActiveRecord::Base
|
|
7
|
-
raise Literal::TypeError.new(
|
|
8
|
-
context: Literal::TypeError::Context.new(
|
|
9
|
-
expected: ActiveRecord::Base, actual: model_class
|
|
10
|
-
)
|
|
11
|
-
)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
6
|
@model_class = model_class
|
|
15
7
|
end
|
|
16
8
|
|
|
@@ -18,10 +10,10 @@ module Literal::Rails
|
|
|
18
10
|
|
|
19
11
|
def ===(value)
|
|
20
12
|
case value
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
when ActiveRecord::Relation, ActiveRecord::Associations::CollectionProxy, ActiveRecord::AssociationRelation
|
|
14
|
+
@model_class == value.model || value.model < @model_class
|
|
15
|
+
else
|
|
16
|
+
false
|
|
25
17
|
end
|
|
26
18
|
end
|
|
27
19
|
end
|
data/lib/literal/result.rb
CHANGED
|
@@ -1,4 +1,72 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Literal::Result
|
|
4
|
+
class Thrown
|
|
5
|
+
def initialize(result)
|
|
6
|
+
@result = result
|
|
7
|
+
freeze
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :result
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Emitter
|
|
14
|
+
def initialize(type:, ball:)
|
|
15
|
+
@type = type
|
|
16
|
+
@ball = ball
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def success(value)
|
|
21
|
+
throw(@ball, Thrown.new(@type.success(value)))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def failure(error)
|
|
25
|
+
throw(@ball, Thrown.new(@type.failure(error)))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Generic
|
|
30
|
+
include Literal::Type
|
|
31
|
+
|
|
32
|
+
def initialize(success_type, failure_type)
|
|
33
|
+
@success_type = success_type
|
|
34
|
+
@failure_type = failure_type
|
|
35
|
+
|
|
36
|
+
freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :success_type, :failure_type
|
|
40
|
+
|
|
41
|
+
def ===(object)
|
|
42
|
+
case object
|
|
43
|
+
when Literal::Success
|
|
44
|
+
@success_type === object.value!
|
|
45
|
+
when Literal::Failure
|
|
46
|
+
@failure_type === object.error!
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def success(value)
|
|
51
|
+
Literal::Success.new(
|
|
52
|
+
value,
|
|
53
|
+
success_type: @success_type,
|
|
54
|
+
failure_type: @failure_type
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def failure(error)
|
|
59
|
+
Literal::Failure.new(
|
|
60
|
+
error,
|
|
61
|
+
success_type: @success_type,
|
|
62
|
+
failure_type: @failure_type
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
freeze
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def handle(&)
|
|
70
|
+
Literal::ResultHandler.new(self).handle(&)
|
|
71
|
+
end
|
|
4
72
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Literal::ResultHandler
|
|
4
|
+
def initialize(result)
|
|
5
|
+
@result = result
|
|
6
|
+
@success_cases = []
|
|
7
|
+
@failure_cases = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def handle
|
|
11
|
+
return @result unless block_given?
|
|
12
|
+
|
|
13
|
+
yield(self)
|
|
14
|
+
|
|
15
|
+
unless Literal.subtype?(@result.success_type, covered_type(@success_cases))
|
|
16
|
+
raise Literal::ArgumentError.new("No success handler covers #{@result.success_type.inspect}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless Literal.subtype?(@result.failure_type, covered_type(@failure_cases))
|
|
20
|
+
raise Literal::ArgumentError.new("No failure handler covers #{@result.failure_type.inspect}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
case @result
|
|
24
|
+
when Literal::Success
|
|
25
|
+
@success_cases.each do |type, block|
|
|
26
|
+
if type === @result.value!
|
|
27
|
+
return block&.call(@result.value!)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
when Literal::Failure
|
|
31
|
+
@failure_cases.each do |type, block|
|
|
32
|
+
if type === @result.error!
|
|
33
|
+
return block&.call(@result.error!)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
raise Literal::ArgumentError.new("Unhandled result type: #{@result.class}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def success(type = Literal::Types::_Any?, &block)
|
|
42
|
+
@success_cases << [type, block]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def failure(type = Literal::Types::_Any?, &block)
|
|
46
|
+
@failure_cases << [type, block]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private def covered_type(cases)
|
|
50
|
+
return Literal::Types::NeverType::Instance if cases.empty?
|
|
51
|
+
|
|
52
|
+
Literal::Types::_Union(*cases.map(&:first))
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/literal/struct.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
class Literal::Struct < Literal::DataStructure
|
|
4
4
|
class << self
|
|
5
|
-
def prop(name, type, kind = :keyword, reader: :public, writer: :public, default: nil)
|
|
5
|
+
def prop(name, type, kind = :keyword, reader: :public, writer: :public, default: nil, description: nil)
|
|
6
6
|
super
|
|
7
7
|
end
|
|
8
8
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Literal::SubtypeContext
|
|
4
|
+
def initialize
|
|
5
|
+
@memo = {}
|
|
6
|
+
@in_progress = {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :memo, :in_progress
|
|
10
|
+
|
|
11
|
+
def memoized?(key)
|
|
12
|
+
@memo.key?(key)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fetch(key)
|
|
16
|
+
@memo[key]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def store(key, result)
|
|
20
|
+
@memo[key] = result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def acquire(key)
|
|
24
|
+
return false if @in_progress.key?(key)
|
|
25
|
+
|
|
26
|
+
@in_progress[key] = true
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def release(key)
|
|
31
|
+
@in_progress.delete(key)
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/literal/success.rb
CHANGED
|
@@ -1,7 +1,103 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Literal::Success < Literal::Result
|
|
4
|
-
|
|
4
|
+
class Generic
|
|
5
|
+
include Literal::Type
|
|
6
|
+
|
|
7
|
+
def initialize(type)
|
|
8
|
+
@type = type
|
|
9
|
+
freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :type
|
|
13
|
+
|
|
14
|
+
def ===(object)
|
|
15
|
+
Literal::Success === object && @type === object.value!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def inspect
|
|
19
|
+
"Literal::Success(#{@type.inspect})"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(value, success_type:, failure_type:)
|
|
5
26
|
@value = value
|
|
27
|
+
|
|
28
|
+
@success_type = success_type
|
|
29
|
+
@failure_type = failure_type
|
|
30
|
+
|
|
31
|
+
Literal.check(@value, success_type)
|
|
32
|
+
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :success_type, :failure_type
|
|
37
|
+
|
|
38
|
+
def success?
|
|
39
|
+
true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def failure?
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def value!
|
|
47
|
+
@value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def deconstruct
|
|
51
|
+
[@value]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def deconstruct_keys(keys)
|
|
55
|
+
if @value.respond_to?(:deconstruct_keys)
|
|
56
|
+
@value.deconstruct_keys(keys)
|
|
57
|
+
else
|
|
58
|
+
{}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def error!
|
|
63
|
+
raise Literal::ArgumentError.new("Success has no error")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def map(type)
|
|
67
|
+
raise ArgumentError unless block_given?
|
|
68
|
+
result = yield(@value)
|
|
69
|
+
|
|
70
|
+
Literal::Success.new(
|
|
71
|
+
result,
|
|
72
|
+
success_type: type,
|
|
73
|
+
failure_type: @failure_type
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def then
|
|
78
|
+
raise ArgumentError unless block_given?
|
|
79
|
+
result = yield(@value)
|
|
80
|
+
|
|
81
|
+
case result
|
|
82
|
+
when Literal::Failure
|
|
83
|
+
Literal::Failure.new(
|
|
84
|
+
result.error!,
|
|
85
|
+
success_type: result.success_type,
|
|
86
|
+
failure_type: Literal::Types::_Union(@failure_type, result.failure_type)
|
|
87
|
+
)
|
|
88
|
+
when Literal::Success
|
|
89
|
+
Literal::Success.new(
|
|
90
|
+
result.value!,
|
|
91
|
+
success_type: result.success_type,
|
|
92
|
+
failure_type: Literal::Types::_Union(@failure_type, result.failure_type)
|
|
93
|
+
)
|
|
94
|
+
else
|
|
95
|
+
raise Literal::ArgumentError.new("Expected block to return a Literal::Result, got #{result.class.inspect}")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def value_or
|
|
100
|
+
raise ArgumentError unless block_given?
|
|
101
|
+
@value
|
|
6
102
|
end
|
|
7
103
|
end
|
data/lib/literal/tuple.rb
CHANGED
|
@@ -26,7 +26,7 @@ class Literal::Tuple
|
|
|
26
26
|
true
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def >=(other)
|
|
29
|
+
def >=(other, context: nil)
|
|
30
30
|
case other
|
|
31
31
|
when Literal::Tuple::Generic
|
|
32
32
|
types = @types
|
|
@@ -36,7 +36,7 @@ class Literal::Tuple
|
|
|
36
36
|
|
|
37
37
|
i, len = 0, types.size
|
|
38
38
|
while i < len
|
|
39
|
-
return false unless Literal.subtype?(other_types[i], types[i])
|
|
39
|
+
return false unless Literal.subtype?(other_types[i], types[i], context:)
|
|
40
40
|
i += 1
|
|
41
41
|
end
|
|
42
42
|
|