sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/sorbet-runtime.rb +100 -0
- data/lib/types/_types.rb +245 -0
- data/lib/types/abstract_utils.rb +50 -0
- data/lib/types/boolean.rb +8 -0
- data/lib/types/compatibility_patches.rb +37 -0
- data/lib/types/configuration.rb +368 -0
- data/lib/types/generic.rb +23 -0
- data/lib/types/helpers.rb +31 -0
- data/lib/types/interface_wrapper.rb +158 -0
- data/lib/types/private/abstract/data.rb +36 -0
- data/lib/types/private/abstract/declare.rb +39 -0
- data/lib/types/private/abstract/hooks.rb +43 -0
- data/lib/types/private/abstract/validate.rb +128 -0
- data/lib/types/private/casts.rb +22 -0
- data/lib/types/private/class_utils.rb +102 -0
- data/lib/types/private/decl_state.rb +18 -0
- data/lib/types/private/error_handler.rb +37 -0
- data/lib/types/private/methods/_methods.rb +344 -0
- data/lib/types/private/methods/call_validation.rb +1177 -0
- data/lib/types/private/methods/decl_builder.rb +275 -0
- data/lib/types/private/methods/modes.rb +18 -0
- data/lib/types/private/methods/signature.rb +196 -0
- data/lib/types/private/methods/signature_validation.rb +232 -0
- data/lib/types/private/mixins/mixins.rb +27 -0
- data/lib/types/private/runtime_levels.rb +41 -0
- data/lib/types/private/types/not_typed.rb +23 -0
- data/lib/types/private/types/string_holder.rb +26 -0
- data/lib/types/private/types/void.rb +33 -0
- data/lib/types/profile.rb +27 -0
- data/lib/types/props/_props.rb +165 -0
- data/lib/types/props/constructor.rb +20 -0
- data/lib/types/props/custom_type.rb +84 -0
- data/lib/types/props/decorator.rb +826 -0
- data/lib/types/props/errors.rb +8 -0
- data/lib/types/props/optional.rb +73 -0
- data/lib/types/props/plugin.rb +15 -0
- data/lib/types/props/pretty_printable.rb +106 -0
- data/lib/types/props/serializable.rb +376 -0
- data/lib/types/props/type_validation.rb +98 -0
- data/lib/types/props/utils.rb +49 -0
- data/lib/types/props/weak_constructor.rb +30 -0
- data/lib/types/runtime_profiled.rb +36 -0
- data/lib/types/sig.rb +28 -0
- data/lib/types/struct.rb +8 -0
- data/lib/types/types/base.rb +141 -0
- data/lib/types/types/class_of.rb +38 -0
- data/lib/types/types/enum.rb +42 -0
- data/lib/types/types/fixed_array.rb +60 -0
- data/lib/types/types/fixed_hash.rb +59 -0
- data/lib/types/types/intersection.rb +36 -0
- data/lib/types/types/noreturn.rb +25 -0
- data/lib/types/types/proc.rb +51 -0
- data/lib/types/types/self_type.rb +31 -0
- data/lib/types/types/simple.rb +33 -0
- data/lib/types/types/type_member.rb +7 -0
- data/lib/types/types/type_parameter.rb +23 -0
- data/lib/types/types/type_template.rb +7 -0
- data/lib/types/types/type_variable.rb +31 -0
- data/lib/types/types/typed_array.rb +20 -0
- data/lib/types/types/typed_enumerable.rb +141 -0
- data/lib/types/types/typed_enumerator.rb +22 -0
- data/lib/types/types/typed_hash.rb +29 -0
- data/lib/types/types/typed_range.rb +22 -0
- data/lib/types/types/typed_set.rb +22 -0
- data/lib/types/types/union.rb +59 -0
- data/lib/types/types/untyped.rb +25 -0
- data/lib/types/utils.rb +223 -0
- metadata +122 -15
@@ -0,0 +1,232 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module T::Private::Methods::SignatureValidation
|
5
|
+
Methods = T::Private::Methods
|
6
|
+
Modes = Methods::Modes
|
7
|
+
|
8
|
+
def self.validate(signature)
|
9
|
+
if signature.method_name == :initialize && signature.method.owner.is_a?(Class)
|
10
|
+
# Constructors are special. They look like overrides in terms of a super_method existing,
|
11
|
+
# but in practice, you never call them polymorphically. Conceptually, they're standard
|
12
|
+
# methods (this is consistent with how they're treated in other languages, e.g. Java)
|
13
|
+
if signature.mode != Modes.standard
|
14
|
+
raise "Constructor methods should always be declared using `sig`."
|
15
|
+
end
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
super_method = signature.method.super_method
|
20
|
+
|
21
|
+
if super_method && super_method.owner != signature.method.owner
|
22
|
+
Methods.maybe_run_sig_block_for_method(super_method)
|
23
|
+
super_signature = Methods.signature_for_method(super_method)
|
24
|
+
|
25
|
+
# If the super_method has any kwargs we can't build a
|
26
|
+
# Signature for it, so we'll just skip validation in that case.
|
27
|
+
if !super_signature && !super_method.parameters.select {|kind, _| kind == :rest || kind == :kwrest}.empty?
|
28
|
+
nil
|
29
|
+
else
|
30
|
+
# super_signature can be nil when we're overriding a method (perhaps a builtin) that didn't use
|
31
|
+
# one of the method signature helpers. Use an untyped signature so we can still validate
|
32
|
+
# everything but types.
|
33
|
+
#
|
34
|
+
# We treat these signatures as overridable, that way people can use `.override` with
|
35
|
+
# overrides of builtins. In the future we could try to distinguish when the method is a
|
36
|
+
# builtin and treat non-builtins as non-overridable (so you'd be forced to declare them with
|
37
|
+
# `.overridable`).
|
38
|
+
#
|
39
|
+
super_signature ||= Methods::Signature.new_untyped(method: super_method)
|
40
|
+
|
41
|
+
validate_override_mode(signature, super_signature)
|
42
|
+
validate_override_shape(signature, super_signature)
|
43
|
+
validate_override_types(signature, super_signature)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
validate_non_override_mode(signature)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private_class_method def self.pretty_mode(signature)
|
51
|
+
if signature.mode == Modes.overridable_implementation
|
52
|
+
'.overridable.implementation'
|
53
|
+
else
|
54
|
+
".#{signature.mode}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.validate_override_mode(signature, super_signature)
|
59
|
+
case signature.mode
|
60
|
+
when *Modes::OVERRIDE_MODES
|
61
|
+
if !Modes::OVERRIDABLE_MODES.include?(super_signature.mode)
|
62
|
+
raise "You declared `#{signature.method_name}` as #{pretty_mode(signature)}, but the method it overrides is not declared as `overridable`.\n" \
|
63
|
+
" Parent definition: #{method_loc_str(super_signature.method)}\n" \
|
64
|
+
" Child definition: #{method_loc_str(signature.method)}\n"
|
65
|
+
end
|
66
|
+
when *Modes::IMPLEMENT_MODES
|
67
|
+
if super_signature.mode != Modes.abstract
|
68
|
+
raise "You declared `#{signature.method_name}` as #{pretty_mode(signature)}, but the method it overrides is not declared as abstract.\n" \
|
69
|
+
" Either mark #{super_signature.method_name} as `abstract.` in the parent: #{method_loc_str(super_signature.method)}\n" \
|
70
|
+
" ... or mark #{signature.method_name} as `override.` in the child: #{method_loc_str(signature.method)}\n"
|
71
|
+
end
|
72
|
+
when *Modes::NON_OVERRIDE_MODES
|
73
|
+
if super_signature.mode == Modes.standard
|
74
|
+
# Peaceful
|
75
|
+
elsif super_signature.mode == Modes.abstract
|
76
|
+
raise "You must use `.implementation` when overriding the abstract method `#{signature.method_name}`.\n" \
|
77
|
+
" Abstract definition: #{method_loc_str(super_signature.method)}\n" \
|
78
|
+
" Implementation definition: #{method_loc_str(signature.method)}\n"
|
79
|
+
elsif super_signature.mode != Modes.untyped
|
80
|
+
raise "You must use `.override` when overriding the existing method `#{signature.method_name}`.\n" \
|
81
|
+
" Parent definition: #{method_loc_str(super_signature.method)}\n" \
|
82
|
+
" Child definition: #{method_loc_str(signature.method)}\n"
|
83
|
+
end
|
84
|
+
else
|
85
|
+
raise "Unexpected mode: #{signature.mode}. Please report to #dev-productivity."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.validate_non_override_mode(signature)
|
90
|
+
case signature.mode
|
91
|
+
when Modes.override
|
92
|
+
raise "You marked `#{signature.method_name}` as #{pretty_mode(signature)}, but that method doesn't already exist in this class/module to be overriden.\n" \
|
93
|
+
" Either check for typos and for missing includes or super classes to make the parent method shows up\n" \
|
94
|
+
" ... or remove #{pretty_mode(signature)} here: #{method_loc_str(signature.method)}\n"
|
95
|
+
when Modes.standard, *Modes::NON_OVERRIDE_MODES
|
96
|
+
# Peaceful
|
97
|
+
nil
|
98
|
+
when *Modes::IMPLEMENT_MODES
|
99
|
+
raise "You marked `#{signature.method_name}` as #{pretty_mode(signature)}, but it doesn't match up with a corresponding abstract method.\n" \
|
100
|
+
" Either check for typos and for missing includes or super classes to make the parent method shows up\n" \
|
101
|
+
" ... or remove #{pretty_mode(signature)} here: #{method_loc_str(signature.method)}\n"
|
102
|
+
else
|
103
|
+
raise "Unexpected mode: #{signature.mode}. Please report to #dev-productivity."
|
104
|
+
end
|
105
|
+
|
106
|
+
owner = signature.method.owner
|
107
|
+
if (signature.mode == Modes.abstract || Modes::OVERRIDABLE_MODES.include?(signature.mode)) &&
|
108
|
+
owner.singleton_class?
|
109
|
+
# Given a singleton class, we can check if it belongs to a
|
110
|
+
# module by looking at its superclass; given `module M`,
|
111
|
+
# `M.singleton_class.superclass == Module`, which is not true
|
112
|
+
# for any class.
|
113
|
+
if owner.superclass == Module
|
114
|
+
raise "Defining an overridable class method (via #{pretty_mode(signature)}) " \
|
115
|
+
"on a module is not allowed. Class methods on " \
|
116
|
+
"modules do not get inherited and thus cannot be overridden. For help, ask in " \
|
117
|
+
"#dev-productivity."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.validate_override_shape(signature, super_signature)
|
123
|
+
return if signature.override_allow_incompatible
|
124
|
+
return if super_signature.mode == Modes.untyped
|
125
|
+
|
126
|
+
method_name = signature.method_name
|
127
|
+
mode_verb = super_signature.mode == Modes.abstract ? 'implements' : 'overrides'
|
128
|
+
|
129
|
+
if !signature.has_rest && signature.arg_count < super_signature.arg_count
|
130
|
+
raise "Your definition of `#{method_name}` must accept at least #{super_signature.arg_count} " \
|
131
|
+
"positional arguments to be compatible with the method it #{mode_verb}: " \
|
132
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
133
|
+
end
|
134
|
+
|
135
|
+
if !signature.has_rest && super_signature.has_rest
|
136
|
+
raise "Your definition of `#{method_name}` must have `*#{super_signature.rest_name}` " \
|
137
|
+
"to be compatible with the method it #{mode_verb}: " \
|
138
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
139
|
+
end
|
140
|
+
|
141
|
+
if signature.req_arg_count > super_signature.req_arg_count
|
142
|
+
raise "Your definition of `#{method_name}` must have no more than #{super_signature.req_arg_count} " \
|
143
|
+
"required argument(s) to be compatible with the method it #{mode_verb}: " \
|
144
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
145
|
+
end
|
146
|
+
|
147
|
+
if !signature.has_keyrest
|
148
|
+
# O(nm), but n and m are tiny here
|
149
|
+
missing_kwargs = super_signature.kwarg_names - signature.kwarg_names
|
150
|
+
if !missing_kwargs.empty?
|
151
|
+
raise "Your definition of `#{method_name}` is missing these keyword arg(s): #{missing_kwargs} " \
|
152
|
+
"which are defined in the method it #{mode_verb}: " \
|
153
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
if !signature.has_keyrest && super_signature.has_keyrest
|
158
|
+
raise "Your definition of `#{method_name}` must have `**#{super_signature.keyrest_name}` " \
|
159
|
+
"to be compatible with the method it #{mode_verb}: " \
|
160
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# O(nm), but n and m are tiny here
|
165
|
+
extra_req_kwargs = signature.req_kwarg_names - super_signature.req_kwarg_names
|
166
|
+
if !extra_req_kwargs.empty?
|
167
|
+
raise "Your definition of `#{method_name}` has extra required keyword arg(s) " \
|
168
|
+
"#{extra_req_kwargs} relative to the method it #{mode_verb}, making it incompatible: " \
|
169
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
170
|
+
end
|
171
|
+
|
172
|
+
if super_signature.block_name && !signature.block_name
|
173
|
+
raise "Your definition of `#{method_name}` must accept a block parameter to be compatible " \
|
174
|
+
"with the method it #{mode_verb}: " \
|
175
|
+
"#{base_override_loc_str(signature, super_signature)}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.validate_override_types(signature, super_signature)
|
180
|
+
return if signature.override_allow_incompatible
|
181
|
+
return if super_signature.mode == Modes.untyped
|
182
|
+
return unless [signature, super_signature].all? do |sig|
|
183
|
+
sig.check_level == :always || (sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
|
184
|
+
end
|
185
|
+
mode_noun = super_signature.mode == Modes.abstract ? 'implementation' : 'override'
|
186
|
+
|
187
|
+
# arg types must be contravariant
|
188
|
+
super_signature.arg_types.zip(signature.arg_types).each_with_index do |((_super_name, super_type), (name, type)), index|
|
189
|
+
if !super_type.subtype_of?(type)
|
190
|
+
raise "Incompatible type for arg ##{index + 1} (`#{name}`) in #{mode_noun} of method " \
|
191
|
+
"`#{signature.method_name}`:\n" \
|
192
|
+
"* Base: `#{super_type}` (in #{method_loc_str(super_signature.method)})\n" \
|
193
|
+
"* #{mode_noun.capitalize}: `#{type}` (in #{method_loc_str(signature.method)})\n" \
|
194
|
+
"(The types must be contravariant.)"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# kwarg types must be contravariant
|
199
|
+
super_signature.kwarg_types.each do |name, super_type|
|
200
|
+
type = signature.kwarg_types[name]
|
201
|
+
if !super_type.subtype_of?(type)
|
202
|
+
raise "Incompatible type for arg `#{name}` in #{mode_noun} of method `#{signature.method_name}`:\n" \
|
203
|
+
"* Base: `#{super_type}` (in #{method_loc_str(super_signature.method)})\n" \
|
204
|
+
"* #{mode_noun.capitalize}: `#{type}` (in #{method_loc_str(signature.method)})\n" \
|
205
|
+
"(The types must be contravariant.)"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# return types must be covariant
|
210
|
+
if !signature.return_type.subtype_of?(super_signature.return_type)
|
211
|
+
raise "Incompatible return type in #{mode_noun} of method `#{signature.method_name}`:\n" \
|
212
|
+
"* Base: `#{super_signature.return_type}` (in #{method_loc_str(super_signature.method)})\n" \
|
213
|
+
"* #{mode_noun.capitalize}: `#{signature.return_type}` (in #{method_loc_str(signature.method)})\n" \
|
214
|
+
"(The types must be covariant.)"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
private_class_method def self.base_override_loc_str(signature, super_signature)
|
219
|
+
mode_noun = super_signature.mode == Modes.abstract ? 'Implementation' : 'Override'
|
220
|
+
"\n * Base definition: in #{method_loc_str(super_signature.method)}" \
|
221
|
+
"\n * #{mode_noun}: in #{method_loc_str(signature.method)}"
|
222
|
+
end
|
223
|
+
|
224
|
+
private_class_method def self.method_loc_str(method)
|
225
|
+
if method.source_location
|
226
|
+
loc = method.source_location.join(':')
|
227
|
+
else
|
228
|
+
loc = "<unknown location>"
|
229
|
+
end
|
230
|
+
"#{method.owner} at #{loc}"
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module T::Private
|
5
|
+
module MixesInClassMethods
|
6
|
+
def included(other)
|
7
|
+
mod = Abstract::Data.get(self, :class_methods_mixin)
|
8
|
+
other.extend(mod)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Mixins
|
14
|
+
def self.declare_mixes_in_class_methods(mixin, class_methods)
|
15
|
+
if mixin.is_a?(Class)
|
16
|
+
raise "Classes cannot be used as mixins, and so mixes_in_class_methods cannot be used on a Class."
|
17
|
+
end
|
18
|
+
|
19
|
+
if Abstract::Data.key?(mixin, :class_methods_mixin)
|
20
|
+
raise "mixes_in_class_methods can only be used once per module."
|
21
|
+
end
|
22
|
+
|
23
|
+
mixin.singleton_class.include(MixesInClassMethods)
|
24
|
+
Abstract::Data.set(mixin, :class_methods_mixin, class_methods)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# Used in `sig.checked(level)` to determine when runtime type checking
|
5
|
+
# is enabled on a method.
|
6
|
+
module T::Private::RuntimeLevels
|
7
|
+
LEVELS = [
|
8
|
+
# Validate every call in every environment
|
9
|
+
:always,
|
10
|
+
# Validate in tests, but not in production
|
11
|
+
:tests,
|
12
|
+
# Don't even validate in tests, b/c too expensive,
|
13
|
+
# or b/c we fully trust the static typing
|
14
|
+
:never,
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
@check_tests = false
|
18
|
+
@wrapped_tests_with_validation = false
|
19
|
+
|
20
|
+
def self.check_tests?
|
21
|
+
# Assume that this code path means that some `sig.checked(:tests)`
|
22
|
+
# has been wrapped (or not wrapped) already, which is a trapdoor
|
23
|
+
# for toggling `@check_tests`.
|
24
|
+
@wrapped_tests_with_validation = true
|
25
|
+
|
26
|
+
@check_tests
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.enable_checking_in_tests
|
30
|
+
if !@check_tests && @wrapped_tests_with_validation
|
31
|
+
raise "Toggle `:tests`-level runtime type checking earlier. " \
|
32
|
+
"There are already some methods wrapped with `sig.checked(:tests)`." \
|
33
|
+
end
|
34
|
+
|
35
|
+
_toggle_checking_tests(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self._toggle_checking_tests(checked)
|
39
|
+
@check_tests = checked
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# A placeholder for when an untyped thing must provide a type.
|
5
|
+
# Raises an exception if it is ever used for validation.
|
6
|
+
class T::Private::Types::NotTyped < T::Types::Base
|
7
|
+
ERROR_MESSAGE = "Validation is being done on a `NotTyped`. Please report to #dev-productivity."
|
8
|
+
|
9
|
+
# @override Base
|
10
|
+
def name
|
11
|
+
"<NOT-TYPED>"
|
12
|
+
end
|
13
|
+
|
14
|
+
# @override Base
|
15
|
+
def valid?(obj)
|
16
|
+
raise ERROR_MESSAGE
|
17
|
+
end
|
18
|
+
|
19
|
+
# @override Base
|
20
|
+
private def subtype_of_single?(other)
|
21
|
+
raise ERROR_MESSAGE
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# Holds a string. Useful for showing type aliases in error messages
|
5
|
+
class T::Private::Types::StringHolder < T::Types::Base
|
6
|
+
attr_reader :string
|
7
|
+
|
8
|
+
def initialize(string)
|
9
|
+
@string = string
|
10
|
+
end
|
11
|
+
|
12
|
+
# @override Base
|
13
|
+
def name
|
14
|
+
string
|
15
|
+
end
|
16
|
+
|
17
|
+
# @override Base
|
18
|
+
def valid?(obj)
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
# @override Base
|
23
|
+
private def subtype_of_single?(other)
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# A marking class for when methods return void.
|
5
|
+
# Should never appear in types directly.
|
6
|
+
class T::Private::Types::Void < T::Types::Base
|
7
|
+
ERROR_MESSAGE = "Validation is being done on an `Void`. Please report to #dev-productivity."
|
8
|
+
|
9
|
+
# The actual return value of `.void` methods.
|
10
|
+
#
|
11
|
+
# Uses `Module.new` because in Ruby an anonymous module will inherit the name
|
12
|
+
# of the constant it's assigned to. This gives it a readable name someone
|
13
|
+
# examines it in Pry or with `#inspect` like:
|
14
|
+
#
|
15
|
+
# T::Private::Types::Void::VOID
|
16
|
+
#
|
17
|
+
VOID = Module.new.freeze
|
18
|
+
|
19
|
+
# @override Base
|
20
|
+
def name
|
21
|
+
"<VOID>"
|
22
|
+
end
|
23
|
+
|
24
|
+
# @override Base
|
25
|
+
def valid?(obj)
|
26
|
+
raise ERROR_MESSAGE
|
27
|
+
end
|
28
|
+
|
29
|
+
# @override Base
|
30
|
+
private def subtype_of_single?(other)
|
31
|
+
raise ERROR_MESSAGE
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module T::Profile
|
5
|
+
SAMPLE_RATE = 101 # 1 out of that many typechecks will be measured
|
6
|
+
class <<self
|
7
|
+
attr_accessor :typecheck_duration
|
8
|
+
attr_accessor :typecheck_samples
|
9
|
+
attr_accessor :typecheck_sample_attempts
|
10
|
+
def typecheck_duration_estimate
|
11
|
+
total_typechecks = typecheck_samples * SAMPLE_RATE + (SAMPLE_RATE - typecheck_sample_attempts)
|
12
|
+
typechecks_measured = typecheck_samples * SAMPLE_RATE
|
13
|
+
typecheck_duration * SAMPLE_RATE * 1.0 * total_typechecks / typechecks_measured
|
14
|
+
end
|
15
|
+
|
16
|
+
def typecheck_count_estimate
|
17
|
+
typecheck_samples * SAMPLE_RATE
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset
|
21
|
+
@typecheck_duration = 0
|
22
|
+
@typecheck_samples = 0
|
23
|
+
@typecheck_sample_attempts = SAMPLE_RATE
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self.reset
|
27
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
# A mixin for defining typed properties (attributes).
|
5
|
+
# To get serialization methods (to/from JSON-style hashes), add T::Props::Serializable.
|
6
|
+
# To get a constructor based on these properties, inherit from T::Struct.
|
7
|
+
module T::Props
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
#####
|
11
|
+
# CAUTION: This mixin is used in hundreds of classes; we want to keep its surface area as narrow
|
12
|
+
# as possible and avoid polluting (and possibly conflicting with) the classes that use it.
|
13
|
+
#
|
14
|
+
# It currently has *zero* instance methods; let's try to keep it that way.
|
15
|
+
# For ClassMethods (below), try to add things to T::Props::Decorator instead unless you are sure
|
16
|
+
# it needs to be exposed here.
|
17
|
+
#####
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
extend T::Sig
|
21
|
+
extend T::Helpers
|
22
|
+
|
23
|
+
def props; decorator.props; end
|
24
|
+
def plugins; @plugins ||= []; end
|
25
|
+
|
26
|
+
def decorator_class; Decorator; end
|
27
|
+
|
28
|
+
def decorator; @decorator ||= decorator_class.new(self); end
|
29
|
+
def reload_decorator!; @decorator = decorator_class.new(self); end
|
30
|
+
|
31
|
+
# @!method self.prop(name, type, opts={})
|
32
|
+
#
|
33
|
+
# Define a new property. See {file:README.md} for some concrete
|
34
|
+
# examples.
|
35
|
+
#
|
36
|
+
# Defining a property defines a method with the same name as the
|
37
|
+
# property, that returns the current value, and a `prop=` method
|
38
|
+
# to set its value. Properties will be inherited by subclasses of
|
39
|
+
# a document class.
|
40
|
+
#
|
41
|
+
# @param name [Symbol] The name of this property
|
42
|
+
# @param cls [Class,T::Props::CustomType] (String) The type of this
|
43
|
+
# property. If the type is itself a {Document} subclass, this
|
44
|
+
# property will be recursively serialized/deserialized.
|
45
|
+
# @param rules [Hash] Options to control this property's behavior.
|
46
|
+
# @option rules [T::Boolean,Symbol] :optional If `true`, this property
|
47
|
+
# is never required to be set before an instance is serialized.
|
48
|
+
# If `:on_load` (default), when this property is missing or nil, a
|
49
|
+
# new model cannot be saved, and an existing model can only be
|
50
|
+
# saved if the property was already missing when it was loaded.
|
51
|
+
# If `false`, when the property is missing/nil after deserialization, it
|
52
|
+
# will be set to the default value (as defined by the `default` or
|
53
|
+
# `factory` option) or will raise if they are not present.
|
54
|
+
# Deprecated: For {Model}s, if `:optional` is set to the special value
|
55
|
+
# `:existing`, the property can be saved as nil even if it was
|
56
|
+
# deserialized with a non-nil value. (Deprecated because there should
|
57
|
+
# never be a need for this behavior; the new behavior of non-optional
|
58
|
+
# properties should be sufficient.)
|
59
|
+
# @option rules [Array] :enum An array of legal values; The
|
60
|
+
# property is required to take on one of those values.
|
61
|
+
# @option rules [T::Boolean] :dont_store If true, this property will
|
62
|
+
# not be saved on the hash resulting from {#serialize}
|
63
|
+
# @option rules [Object] :ifunset A value to be returned if this
|
64
|
+
# property is requested but has never been set (is set to
|
65
|
+
# `nil`). It is applied at property-access time, and never saved
|
66
|
+
# back onto the object or into the database.
|
67
|
+
#
|
68
|
+
# ``:ifunset`` is considered **DEPRECATED** and should not be used
|
69
|
+
# in new code, in favor of just setting a default value.
|
70
|
+
# @option rules [Model, Symbol, Proc] :foreign A model class that this
|
71
|
+
# property is a reference to. Passing `:foreign` will define a
|
72
|
+
# `:"#{name}_"` method, that will load and return the
|
73
|
+
# corresponding foreign model.
|
74
|
+
#
|
75
|
+
# A symbol can be passed to avoid load-order dependencies; It
|
76
|
+
# will be lazily resolved relative to the enclosing module of the
|
77
|
+
# defining class.
|
78
|
+
#
|
79
|
+
# A callable (proc or method) can be passed to dynamically specify the
|
80
|
+
# foreign model. This will be passed the object instance so that other
|
81
|
+
# properties of the object can be used to determine the relevant model
|
82
|
+
# class. It should return a string/symbol class name or the foreign model
|
83
|
+
# class directly.
|
84
|
+
#
|
85
|
+
# @option rules [Object] :default A default value that will be set
|
86
|
+
# by {#initialize} if none is provided in the initialization
|
87
|
+
# hash. This will not affect objects loaded by {.from_hash}.
|
88
|
+
# @option rules [Proc] :factory A `Proc` that will be called to
|
89
|
+
# generate an initial value for this prop on {#initialize}, if
|
90
|
+
# none is providede.
|
91
|
+
# @option rules [T::Boolean] :immutable If true, this prop cannot be
|
92
|
+
# modified after an instance is created or loaded from a hash.
|
93
|
+
# @option rules [Class,T::Props::CustomType] :array If set, specifies the
|
94
|
+
# type of individual members of a prop with `type` `Array`. This
|
95
|
+
# allows for typechecking of arrays, and also for recursive
|
96
|
+
# serialization of arrays of sub-{Document}s.
|
97
|
+
# @option rules [T::Boolean] :override It is an error to redeclare a
|
98
|
+
# `prop` that has already been declared (including on a
|
99
|
+
# superclass), unless `:override` is set to `true`.
|
100
|
+
# @option rules [Symbol, Array] :redaction A redaction directive that may
|
101
|
+
# be passed to Chalk::Tools::RedactionUtils.redact_with_directive to
|
102
|
+
# sanitize this parameter for display. Will define a
|
103
|
+
# `:"#{name}_redacted"` method, which will return the value in sanitized
|
104
|
+
# form.
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
def prop(name, cls, rules={})
|
108
|
+
cls = T::Utils.coerce(cls) if !cls.is_a?(Module)
|
109
|
+
decorator.prop_defined(name, cls, rules)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @!method validate_prop_value(propname, value)
|
113
|
+
#
|
114
|
+
# Validates the value of the specified prop. This method allows the caller to
|
115
|
+
# validate a value for a prop without having to set the data on the instance.
|
116
|
+
# Throws if invalid.
|
117
|
+
#
|
118
|
+
# @param prop [Symbol]
|
119
|
+
# @param val [Object]
|
120
|
+
# @return [void]
|
121
|
+
def validate_prop_value(prop, val)
|
122
|
+
decorator.validate_prop_value(prop, val)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Needs to be documented
|
126
|
+
def plugin(mod)
|
127
|
+
decorator.plugin(mod)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Shorthand helper to define a `prop` with `immutable => true`
|
131
|
+
sig {params(name: T.any(Symbol, String), cls_or_args: T.untyped, args: T::Hash[Symbol, T.untyped]).void}
|
132
|
+
def const(name, cls_or_args, args={})
|
133
|
+
if (cls_or_args.is_a?(Hash) && cls_or_args.key?(:immutable)) || args.key?(:immutable)
|
134
|
+
raise ArgumentError.new("Cannot pass 'immutable' argument when using 'const' keyword to define a prop")
|
135
|
+
end
|
136
|
+
|
137
|
+
if cls_or_args.is_a?(Hash)
|
138
|
+
self.prop(name, cls_or_args.merge(immutable: true))
|
139
|
+
else
|
140
|
+
self.prop(name, cls_or_args, args.merge(immutable: true))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def included(child)
|
145
|
+
decorator.model_inherited(child)
|
146
|
+
super
|
147
|
+
end
|
148
|
+
|
149
|
+
def prepended(child)
|
150
|
+
decorator.model_inherited(child)
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
154
|
+
def extended(child)
|
155
|
+
decorator.model_inherited(child.singleton_class)
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
def inherited(child)
|
160
|
+
decorator.model_inherited(child)
|
161
|
+
super
|
162
|
+
end
|
163
|
+
end
|
164
|
+
mixes_in_class_methods(ClassMethods)
|
165
|
+
end
|