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.
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