sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

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 (69) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +100 -0
  3. data/lib/types/_types.rb +245 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +37 -0
  7. data/lib/types/configuration.rb +368 -0
  8. data/lib/types/generic.rb +23 -0
  9. data/lib/types/helpers.rb +31 -0
  10. data/lib/types/interface_wrapper.rb +158 -0
  11. data/lib/types/private/abstract/data.rb +36 -0
  12. data/lib/types/private/abstract/declare.rb +39 -0
  13. data/lib/types/private/abstract/hooks.rb +43 -0
  14. data/lib/types/private/abstract/validate.rb +128 -0
  15. data/lib/types/private/casts.rb +22 -0
  16. data/lib/types/private/class_utils.rb +102 -0
  17. data/lib/types/private/decl_state.rb +18 -0
  18. data/lib/types/private/error_handler.rb +37 -0
  19. data/lib/types/private/methods/_methods.rb +344 -0
  20. data/lib/types/private/methods/call_validation.rb +1177 -0
  21. data/lib/types/private/methods/decl_builder.rb +275 -0
  22. data/lib/types/private/methods/modes.rb +18 -0
  23. data/lib/types/private/methods/signature.rb +196 -0
  24. data/lib/types/private/methods/signature_validation.rb +232 -0
  25. data/lib/types/private/mixins/mixins.rb +27 -0
  26. data/lib/types/private/runtime_levels.rb +41 -0
  27. data/lib/types/private/types/not_typed.rb +23 -0
  28. data/lib/types/private/types/string_holder.rb +26 -0
  29. data/lib/types/private/types/void.rb +33 -0
  30. data/lib/types/profile.rb +27 -0
  31. data/lib/types/props/_props.rb +165 -0
  32. data/lib/types/props/constructor.rb +20 -0
  33. data/lib/types/props/custom_type.rb +84 -0
  34. data/lib/types/props/decorator.rb +826 -0
  35. data/lib/types/props/errors.rb +8 -0
  36. data/lib/types/props/optional.rb +73 -0
  37. data/lib/types/props/plugin.rb +15 -0
  38. data/lib/types/props/pretty_printable.rb +106 -0
  39. data/lib/types/props/serializable.rb +376 -0
  40. data/lib/types/props/type_validation.rb +98 -0
  41. data/lib/types/props/utils.rb +49 -0
  42. data/lib/types/props/weak_constructor.rb +30 -0
  43. data/lib/types/runtime_profiled.rb +36 -0
  44. data/lib/types/sig.rb +28 -0
  45. data/lib/types/struct.rb +8 -0
  46. data/lib/types/types/base.rb +141 -0
  47. data/lib/types/types/class_of.rb +38 -0
  48. data/lib/types/types/enum.rb +42 -0
  49. data/lib/types/types/fixed_array.rb +60 -0
  50. data/lib/types/types/fixed_hash.rb +59 -0
  51. data/lib/types/types/intersection.rb +36 -0
  52. data/lib/types/types/noreturn.rb +25 -0
  53. data/lib/types/types/proc.rb +51 -0
  54. data/lib/types/types/self_type.rb +31 -0
  55. data/lib/types/types/simple.rb +33 -0
  56. data/lib/types/types/type_member.rb +7 -0
  57. data/lib/types/types/type_parameter.rb +23 -0
  58. data/lib/types/types/type_template.rb +7 -0
  59. data/lib/types/types/type_variable.rb +31 -0
  60. data/lib/types/types/typed_array.rb +20 -0
  61. data/lib/types/types/typed_enumerable.rb +141 -0
  62. data/lib/types/types/typed_enumerator.rb +22 -0
  63. data/lib/types/types/typed_hash.rb +29 -0
  64. data/lib/types/types/typed_range.rb +22 -0
  65. data/lib/types/types/typed_set.rb +22 -0
  66. data/lib/types/types/union.rb +59 -0
  67. data/lib/types/types/untyped.rb +25 -0
  68. data/lib/types/utils.rb +223 -0
  69. 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