Runtime assertions for Ruby
literal.fun
ruby
1# frozen_string_literal: true
2
3require "zeitwerk"
4require_relative "literal/version"
5
6module Literal
7 Loader = Zeitwerk::Loader.for_gem.tap do |loader|
8 loader.ignore("#{__dir__}/literal/rails")
9 loader.ignore("#{__dir__}/literal/railtie.rb")
10 loader.ignore("#{__dir__}/ruby_lsp")
11
12 loader.inflector.inflect(
13 "json_data_type" => "JSONDataType"
14 )
15
16 loader.collapse("#{__dir__}/literal/flags")
17 loader.collapse("#{__dir__}/literal/errors")
18 loader.collapse("#{__dir__}/literal/serializers")
19
20 loader.setup
21 end
22
23 def self.Value(*args, **kwargs, &block)
24 value_class = Class.new(Literal::Value)
25
26 type = Literal::Types._Constraint(*args, **kwargs)
27 value_class.define_method(:__type__) { type }
28
29 if subtype?(type, Integer)
30 value_class.alias_method :to_i, :value
31 elsif subtype?(type, String)
32 value_class.alias_method :to_s, :value
33 value_class.alias_method :to_str, :value
34 elsif subtype?(type, Array)
35 value_class.alias_method :to_a, :value
36 value_class.alias_method :to_ary, :value
37 elsif subtype?(type, Hash)
38 value_class.alias_method :to_h, :value
39 elsif subtype?(type, Float)
40 value_class.alias_method :to_f, :value
41 elsif subtype?(type, Set)
42 value_class.alias_method :to_set, :value
43 end
44
45 value_class.class_eval(&block) if block
46 value_class.freeze
47 end
48
49 def self.Delegator(*args, **kwargs, &block)
50 delegator_class = Class.new(Literal::Delegator)
51
52 type = Literal::Types._Constraint(*args, **kwargs)
53 delegator_class.define_method(:__type__) { type }
54
55 delegator_class.class_eval(&block) if block
56 delegator_class.freeze
57 end
58
59 def self.Enum(type)
60 Class.new(Literal::Enum) do
61 prop :value, type, :positional, reader: :public
62 end
63 end
64
65 def self.Array(type)
66 Literal::Array::Generic.new(type)
67 end
68
69 def self.Set(type)
70 Literal::Set::Generic.new(type)
71 end
72
73 def self.Hash(key_type, value_type)
74 Literal::Hash::Generic.new(key_type, value_type)
75 end
76
77 def self.Tuple(*types)
78 Literal::Tuple::Generic.new(*types)
79 end
80
81 def self.Brand(...)
82 Literal::Brand.new(...)
83 end
84
85 def self.Result(success_type, failure_type, &)
86 result_type = Result::Generic.new(success_type, failure_type)
87
88 if block_given?
89 result_type.try(&)
90 else
91 result_type
92 end
93 end
94
95 def self.Success(type)
96 Success::Generic.new(type)
97 end
98
99 def self.Failure(type)
100 Failure::Generic.new(type)
101 end
102
103 def self.check(value, type)
104 if type === value
105 true
106 else
107 context = Literal::TypeError::Context.new(expected: type, actual: value)
108 type.record_literal_type_errors(context) if type.respond_to?(:record_literal_type_errors)
109 yield context if block_given?
110 raise Literal::TypeError.new(context:)
111 end
112 end
113
114 def self.subtype?(type, supertype, context: nil)
115 context ||= SubtypeContext.new
116 raw_key = [type.object_id, supertype.object_id]
117 raw_acquired = false
118 resolved_key = nil
119 resolved_acquired = false
120
121 return context.fetch(raw_key) if context.memoized?(raw_key)
122 return false unless context.acquire(raw_key)
123
124 raw_acquired = true
125
126 subtype = type
127
128 subtype = subtype.block.call if Types::DeferredType === subtype
129 supertype = supertype.block.call if Types::DeferredType === supertype
130 resolved_key = [subtype.object_id, supertype.object_id]
131
132 if resolved_key != raw_key
133 if context.memoized?(resolved_key)
134 result = context.fetch(resolved_key)
135 context.store(raw_key, result)
136 return result
137 end
138
139 return false unless context.acquire(resolved_key)
140
141 resolved_acquired = true
142 end
143
144 result = if subtype == Types::NeverType::Instance
145 true
146 elsif supertype == subtype
147 true
148 else
149 case supertype
150 when Literal::Type
151 supertype.>=(subtype, context:)
152 when Module
153 case subtype
154 when Module
155 supertype >= subtype
156 when Numeric
157 Numeric >= supertype
158 when String
159 String >= supertype
160 when Symbol
161 Symbol >= supertype
162 when ::Array
163 ::Array >= supertype
164 when ::Hash
165 ::Hash >= supertype
166 when Literal::Type
167 subtype.<=(supertype, context:)
168 else
169 false
170 end
171 when Range
172 supertype.cover?(subtype)
173 else
174 false
175 end
176 end
177
178 context.store(resolved_key, result)
179 context.store(raw_key, result)
180 result
181 ensure
182 if context
183 context.release(resolved_key) if resolved_acquired
184 context.release(raw_key) if raw_acquired
185 end
186 end
187end
188
189require_relative "literal/railtie" if defined?(Rails)