sorbet-runtime 0.5.12155 → 0.6.12632

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/types/_types.rb +1 -1
  3. data/lib/types/boolean.rb +1 -1
  4. data/lib/types/configuration.rb +4 -14
  5. data/lib/types/enum.rb +35 -35
  6. data/lib/types/non_forcing_constants.rb +1 -1
  7. data/lib/types/private/abstract/data.rb +1 -1
  8. data/lib/types/private/methods/_methods.rb +21 -5
  9. data/lib/types/private/methods/call_validation.rb +3 -16
  10. data/lib/types/private/methods/decl_builder.rb +6 -1
  11. data/lib/types/private/methods/signature.rb +8 -8
  12. data/lib/types/private/methods/signature_validation.rb +42 -3
  13. data/lib/types/private/mixins/mixins.rb +1 -1
  14. data/lib/types/private/runtime_levels.rb +1 -1
  15. data/lib/types/private/sealed.rb +5 -5
  16. data/lib/types/props/_props.rb +6 -9
  17. data/lib/types/props/constructor.rb +1 -1
  18. data/lib/types/props/custom_type.rb +5 -5
  19. data/lib/types/props/decorator.rb +127 -64
  20. data/lib/types/props/generated_code_validation.rb +4 -4
  21. data/lib/types/props/has_lazily_specialized_methods.rb +13 -13
  22. data/lib/types/props/optional.rb +2 -2
  23. data/lib/types/props/pretty_printable.rb +6 -6
  24. data/lib/types/props/private/apply_default.rb +15 -15
  25. data/lib/types/props/private/deserializer_generator.rb +10 -6
  26. data/lib/types/props/private/serde_transform.rb +8 -8
  27. data/lib/types/props/private/serializer_generator.rb +9 -5
  28. data/lib/types/props/private/setter_factory.rb +4 -4
  29. data/lib/types/props/serializable.rb +14 -19
  30. data/lib/types/props/type_validation.rb +5 -5
  31. data/lib/types/props/utils.rb +1 -1
  32. data/lib/types/props/weak_constructor.rb +3 -3
  33. data/lib/types/sig.rb +2 -2
  34. data/lib/types/struct.rb +2 -2
  35. data/lib/types/types/base.rb +6 -6
  36. data/lib/types/types/fixed_array.rb +1 -1
  37. data/lib/types/types/fixed_hash.rb +6 -6
  38. data/lib/types/types/intersection.rb +2 -2
  39. data/lib/types/types/t_enum.rb +2 -0
  40. data/lib/types/types/union.rb +7 -7
  41. metadata +3 -3
@@ -5,7 +5,7 @@ module T::Private::Sealed
5
5
  module NoInherit
6
6
  def inherited(child)
7
7
  super
8
- caller_loc = T::Private::CallerUtils.find_caller {|loc| loc.base_label != 'inherited'}
8
+ caller_loc = T::Private::CallerUtils.find_caller { |loc| loc.base_label != 'inherited' }
9
9
  T::Private::Sealed.validate_inheritance(caller_loc, self, child, 'inherited')
10
10
  @sorbet_sealed_module_all_subclasses << child
11
11
  end
@@ -22,14 +22,14 @@ module T::Private::Sealed
22
22
  module NoIncludeExtend
23
23
  def included(child)
24
24
  super
25
- caller_loc = T::Private::CallerUtils.find_caller {|loc| loc.base_label != 'included'}
25
+ caller_loc = T::Private::CallerUtils.find_caller { |loc| loc.base_label != 'included' }
26
26
  T::Private::Sealed.validate_inheritance(caller_loc, self, child, 'included')
27
27
  @sorbet_sealed_module_all_subclasses << child
28
28
  end
29
29
 
30
30
  def extended(child)
31
31
  super
32
- caller_loc = T::Private::CallerUtils.find_caller {|loc| loc.base_label != 'extended'}
32
+ caller_loc = T::Private::CallerUtils.find_caller { |loc| loc.base_label != 'extended' }
33
33
  T::Private::Sealed.validate_inheritance(caller_loc, self, child, 'extended')
34
34
  @sorbet_sealed_module_all_subclasses << child
35
35
  end
@@ -70,7 +70,7 @@ module T::Private::Sealed
70
70
 
71
71
  def self.validate_inheritance(caller_loc, parent, child, verb)
72
72
  this_file = caller_loc&.path
73
- decl_file = parent.instance_variable_get(:@sorbet_sealed_module_decl_file) if sealed_module?(parent)
73
+ decl_file = parent.instance_variable_get(:@sorbet_sealed_module_decl_file)
74
74
 
75
75
  if !this_file
76
76
  raise "Could not use backtrace to determine file for #{verb} child #{child}"
@@ -81,7 +81,7 @@ module T::Private::Sealed
81
81
 
82
82
  if !this_file.start_with?(decl_file)
83
83
  whitelist = T::Configuration.sealed_violation_whitelist
84
- if !whitelist.nil? && whitelist.any? {|pattern| this_file =~ pattern}
84
+ if !whitelist.nil? && whitelist.any? { |pattern| this_file =~ pattern }
85
85
  return
86
86
  end
87
87
 
@@ -109,7 +109,7 @@ module T::Props
109
109
  # form.
110
110
  #
111
111
  # @return [void]
112
- sig {params(name: Symbol, cls: T.untyped, rules: T.untyped).void}
112
+ sig { params(name: Symbol, cls: T.untyped, rules: T.untyped).void }
113
113
  def prop(name, cls, **rules)
114
114
  cls = T::Utils.coerce(cls) if !cls.is_a?(Module)
115
115
  decorator.prop_defined(name, cls, rules)
@@ -132,17 +132,14 @@ module T::Props
132
132
  end
133
133
 
134
134
  # Shorthand helper to define a `prop` with `immutable => true`
135
- sig {params(name: Symbol, cls_or_args: T.untyped, args: T.untyped).void}
136
- def const(name, cls_or_args, **args)
137
- if (cls_or_args.is_a?(Hash) && cls_or_args.key?(:immutable)) || args.key?(:immutable)
135
+ sig { params(name: Symbol, cls: T.untyped, rules: T.untyped).void }
136
+ def const(name, cls, **rules)
137
+ if rules.key?(:immutable)
138
138
  Kernel.raise ArgumentError.new("Cannot pass 'immutable' argument when using 'const' keyword to define a prop")
139
139
  end
140
140
 
141
- if cls_or_args.is_a?(Hash)
142
- self.prop(name, **cls_or_args.merge(immutable: true))
143
- else
144
- self.prop(name, cls_or_args, **args.merge(immutable: true))
145
- end
141
+ rules[:immutable] = true
142
+ self.prop(name, cls, **rules)
146
143
  end
147
144
 
148
145
  def included(child)
@@ -15,7 +15,7 @@ module T::Props::Constructor::DecoratorMethods
15
15
  # we'll use to check for any unrecognized input.)
16
16
  #
17
17
  # checked(:never) - O(runtime object construction)
18
- sig {params(instance: T::Props::Constructor, hash: T::Hash[Symbol, T.untyped]).returns(Integer).checked(:never)}
18
+ sig { params(instance: T::Props::Constructor, hash: T::Hash[Symbol, T.untyped]).returns(Integer).checked(:never) }
19
19
  def construct_props_without_defaults(instance, hash)
20
20
  # Use `each_pair` rather than `count` because, as of Ruby 2.6, the latter delegates to Enumerator
21
21
  # and therefore allocates for each entry.
@@ -39,7 +39,7 @@ module T::Props
39
39
  #
40
40
  # @param [Object] instance
41
41
  # @return An instance of one of T::Configuration.scalar_types
42
- sig {abstract.params(instance: T.untyped).returns(T.untyped).checked(:never)}
42
+ sig { abstract.params(instance: T.untyped).returns(T.untyped).checked(:never) }
43
43
  def serialize(instance); end
44
44
 
45
45
  # Given the serialized form of your type, this returns an instance
@@ -47,17 +47,17 @@ module T::Props
47
47
  #
48
48
  # @param scalar One of T::Configuration.scalar_types
49
49
  # @return Object
50
- sig {abstract.params(scalar: T.untyped).returns(T.untyped).checked(:never)}
50
+ sig { abstract.params(scalar: T.untyped).returns(T.untyped).checked(:never) }
51
51
  def deserialize(scalar); end
52
52
 
53
- sig {override.params(_base: Module).void}
53
+ sig { override.params(_base: Module).void }
54
54
  def self.included(_base)
55
55
  super
56
56
 
57
57
  raise 'Please use "extend", not "include" to attach this module'
58
58
  end
59
59
 
60
- sig(:final) {params(val: T.untyped).returns(T::Boolean).checked(:never)}
60
+ sig(:final) { params(val: T.untyped).returns(T::Boolean).checked(:never) }
61
61
  def self.scalar_type?(val)
62
62
  # We don't need to check for val's included modules in
63
63
  # T::Configuration.scalar_types, because T::Configuration.scalar_types
@@ -74,7 +74,7 @@ module T::Props
74
74
  # implement set-like fields that store a unique-array, but forbid
75
75
  # hashes; Custom hash types should be implemented via an emebdded
76
76
  # T::Struct (or a subclass like Chalk::ODM::Document) or via T.
77
- sig(:final) {params(val: Object).returns(T::Boolean).checked(:never)}
77
+ sig(:final) { params(val: Object).returns(T::Boolean).checked(:never) }
78
78
  def self.valid_serialization?(val)
79
79
  case val
80
80
  when Array
@@ -10,17 +10,33 @@
10
10
  class T::Props::Decorator
11
11
  extend T::Sig
12
12
 
13
- Rules = T.type_alias {T::Hash[Symbol, T.untyped]}
14
- DecoratedInstance = T.type_alias {Object} # Would be T::Props, but that produces circular reference errors in some circumstances
15
- PropType = T.type_alias {T::Types::Base}
16
- PropTypeOrClass = T.type_alias {T.any(PropType, Module)}
13
+ Rules = T.type_alias { T::Hash[Symbol, T.untyped] }
14
+ DecoratedInstance = T.type_alias { Object } # Would be T::Props, but that produces circular reference errors in some circumstances
15
+ PropType = T.type_alias { T::Types::Base }
16
+ PropTypeOrClass = T.type_alias { T.any(PropType, Module) }
17
+ OverrideRules = T.type_alias { T::Hash[Symbol, {allow_incompatible: T::Boolean}] }
17
18
 
18
19
  class NoRulesError < StandardError; end
19
20
 
20
21
  EMPTY_PROPS = T.let({}.freeze, T::Hash[Symbol, Rules], checked: false)
21
22
  private_constant :EMPTY_PROPS
22
23
 
23
- sig {params(klass: T.untyped).void.checked(:never)}
24
+ OVERRIDE_TRUE = T.let({
25
+ reader: {allow_incompatible: false}.freeze,
26
+ writer: {allow_incompatible: false}.freeze,
27
+ }.freeze, OverrideRules)
28
+
29
+ OVERRIDE_READER = T.let({
30
+ reader: {allow_incompatible: false}.freeze,
31
+ }.freeze, OverrideRules)
32
+
33
+ OVERRIDE_WRITER = T.let({
34
+ writer: {allow_incompatible: false}.freeze,
35
+ }.freeze, OverrideRules)
36
+
37
+ OVERRIDE_EMPTY = T.let({}.freeze, OverrideRules)
38
+
39
+ sig { params(klass: T.untyped).void.checked(:never) }
24
40
  def initialize(klass)
25
41
  @class = T.let(klass, T.all(Module, T::Props::ClassMethods))
26
42
  @class.plugins.each do |mod|
@@ -30,32 +46,30 @@ class T::Props::Decorator
30
46
  end
31
47
 
32
48
  # checked(:never) - O(prop accesses)
33
- sig {returns(T::Hash[Symbol, Rules]).checked(:never)}
49
+ sig { returns(T::Hash[Symbol, Rules]).checked(:never) }
34
50
  attr_reader :props
35
51
 
36
- sig {returns(T::Array[Symbol])}
52
+ sig { returns(T::Array[Symbol]) }
37
53
  def all_props
38
54
  props.keys
39
55
  end
40
56
 
41
57
  # checked(:never) - O(prop accesses)
42
- sig {params(prop: T.any(Symbol, String)).returns(Rules).checked(:never)}
58
+ sig { params(prop: T.any(Symbol, String)).returns(Rules).checked(:never) }
43
59
  def prop_rules(prop)
44
60
  props[prop.to_sym] || raise("No such prop: #{prop.inspect}")
45
61
  end
46
62
 
47
63
  # checked(:never) - Rules hash is expensive to check
48
- sig {params(prop: Symbol, rules: Rules).void.checked(:never)}
49
- def add_prop_definition(prop, rules)
64
+ sig { params(name: Symbol, rules: Rules).void.checked(:never) }
65
+ def add_prop_definition(name, rules)
50
66
  override = rules.delete(:override)
51
67
 
52
- if props.include?(prop) && !override
53
- raise ArgumentError.new("Attempted to redefine prop #{prop.inspect} on class #{@class} that's already defined without specifying :override => true: #{prop_rules(prop)}")
54
- elsif !props.include?(prop) && override
55
- raise ArgumentError.new("Attempted to override a prop #{prop.inspect} on class #{@class} that doesn't already exist")
68
+ if props.include?(name) && !override
69
+ raise ArgumentError.new("Attempted to redefine prop #{name.inspect} on class #{@class} that's already defined without specifying :override => true: #{prop_rules(name)}")
56
70
  end
57
71
 
58
- @props = @props.merge(prop => rules.freeze).freeze
72
+ @props = @props.merge(name => rules.freeze).freeze
59
73
  end
60
74
 
61
75
  # Heads up!
@@ -79,16 +93,16 @@ class T::Props::Decorator
79
93
  extra
80
94
  setter_validate
81
95
  _tnilable
82
- ].to_h {|k| [k, true]}.freeze, T::Hash[Symbol, T::Boolean], checked: false)
96
+ ].to_h { |k| [k, true] }.freeze, T::Hash[Symbol, T::Boolean], checked: false)
83
97
  private_constant :VALID_RULE_KEYS
84
98
 
85
- sig {params(key: Symbol).returns(T::Boolean).checked(:never)}
99
+ sig { params(key: Symbol).returns(T::Boolean).checked(:never) }
86
100
  def valid_rule_key?(key)
87
101
  !!VALID_RULE_KEYS[key]
88
102
  end
89
103
 
90
104
  # checked(:never) - O(prop accesses)
91
- sig {returns(T.all(Module, T::Props::ClassMethods)).checked(:never)}
105
+ sig { returns(T.all(Module, T::Props::ClassMethods)).checked(:never) }
92
106
  def decorated_class
93
107
  @class
94
108
  end
@@ -98,7 +112,7 @@ class T::Props::Decorator
98
112
  # Use this to validate that a value will validate for a given prop. Useful for knowing whether a value can be set on a model without setting it.
99
113
  #
100
114
  # checked(:never) - potentially O(prop accesses) depending on usage pattern
101
- sig {params(prop: Symbol, val: T.untyped).void.checked(:never)}
115
+ sig { params(prop: Symbol, val: T.untyped).void.checked(:never) }
102
116
  def validate_prop_value(prop, val)
103
117
  prop_rules(prop).fetch(:value_validate_proc).call(val)
104
118
  end
@@ -160,7 +174,9 @@ class T::Props::Decorator
160
174
  .checked(:never)
161
175
  end
162
176
  def prop_get(instance, prop, rules=prop_rules(prop))
163
- val = instance.instance_variable_get(rules[:accessor_key]) if instance.instance_variable_defined?(rules[:accessor_key])
177
+ # `instance_variable_get` will return nil if the variable doesn't exist
178
+ # which is what we want to have happen for the logic below.
179
+ val = instance.instance_variable_get(rules[:accessor_key])
164
180
  if !val.nil?
165
181
  val
166
182
  elsif (d = rules[:ifunset])
@@ -180,7 +196,9 @@ class T::Props::Decorator
180
196
  .checked(:never)
181
197
  end
182
198
  def prop_get_if_set(instance, prop, rules=prop_rules(prop))
183
- instance.instance_variable_get(rules[:accessor_key]) if instance.instance_variable_defined?(rules[:accessor_key])
199
+ # `instance_variable_get` will return nil if the variable doesn't exist
200
+ # which is what we want to have happen for the return value here.
201
+ instance.instance_variable_get(rules[:accessor_key])
184
202
  end
185
203
  alias_method :get, :prop_get_if_set # Alias for backwards compatibility
186
204
 
@@ -202,7 +220,7 @@ class T::Props::Decorator
202
220
  end
203
221
 
204
222
  # TODO: we should really be checking all the methods on `cls`, not just Object
205
- BANNED_METHOD_NAMES = T.let(Object.instance_methods.each_with_object({}) {|x, acc| acc[x] = true}.freeze, T::Hash[Symbol, TrueClass], checked: false)
223
+ BANNED_METHOD_NAMES = T.let(Object.instance_methods.each_with_object({}) { |x, acc| acc[x] = true }.freeze, T::Hash[Symbol, TrueClass], checked: false)
206
224
 
207
225
  # checked(:never) - Rules hash is expensive to check
208
226
  sig do
@@ -223,8 +241,10 @@ class T::Props::Decorator
223
241
  "to 'sensitivity:' (in prop #{@class.name}.#{name})")
224
242
  end
225
243
 
226
- if rules.keys.any? {|k| !valid_rule_key?(k)}
227
- raise ArgumentError.new("At least one invalid prop arg supplied in #{self}: #{rules.keys.inspect}")
244
+ if rules.keys.any? { |k| !valid_rule_key?(k) }
245
+ invalid_keys = rules.keys.reject { |k| valid_rule_key?(k) }
246
+ suffix = invalid_keys.size == 1 ? "" : "s"
247
+ raise ArgumentError.new("Invalid prop arg#{suffix} supplied in #{self}: #{invalid_keys.inspect}")
228
248
  end
229
249
 
230
250
  if !rules[:clobber_existing_method!] && !rules[:without_accessors] && BANNED_METHOD_NAMES.include?(name.to_sym)
@@ -245,9 +265,11 @@ class T::Props::Decorator
245
265
  end
246
266
 
247
267
  SAFE_NAME = T.let(/\A[A-Za-z_][A-Za-z0-9_-]*\z/.freeze, Regexp, checked: false)
268
+ # Should be exactly the same as `SAFE_NAME`, but with a leading `@`.
269
+ SAFE_ACCESSOR_KEY_NAME = T.let(/\A@[A-Za-z_][A-Za-z0-9_-]*\z/.freeze, Regexp, checked: false)
248
270
 
249
271
  # Used to validate both prop names and serialized forms
250
- sig {params(name: T.any(Symbol, String)).void.checked(:never)}
272
+ sig { params(name: T.any(Symbol, String)).void.checked(:never) }
251
273
  private def validate_prop_name(name)
252
274
  if !name.match?(SAFE_NAME)
253
275
  raise ArgumentError.new("Invalid prop name in #{@class.name}: #{name}")
@@ -255,7 +277,7 @@ class T::Props::Decorator
255
277
  end
256
278
 
257
279
  # This converts the type from a T::Type to a regular old ruby class.
258
- sig {params(type: T::Types::Base).returns(Module).checked(:never)}
280
+ sig { params(type: T::Types::Base).returns(Module).checked(:never) }
259
281
  private def convert_type_to_class(type)
260
282
  case type
261
283
  when T::Types::TypedArray, T::Types::FixedArray
@@ -302,6 +324,30 @@ class T::Props::Decorator
302
324
  T::Utils::Nilable.is_union_with_nilclass(cls) || ((cls == T.untyped || cls == NilClass) && rules.key?(:default) && rules[:default].nil?)
303
325
  end
304
326
 
327
+ sig(:final) { params(name: Symbol).returns(T::Boolean).checked(:never) }
328
+ private def method_defined_on_ancestor?(name)
329
+ (@class.method_defined?(name) || @class.private_method_defined?(name)) &&
330
+ # Unfortunately, older versions of ruby don't allow the second parameter on
331
+ # `private_method_defined?`.
332
+ (!@class.method_defined?(name, false) && !@class.private_method_defined?(name, false))
333
+ end
334
+
335
+ sig(:final) { params(name: Symbol, rules: Rules).void.checked(:never) }
336
+ private def validate_overrides(name, rules)
337
+ override = elaborate_override(name, rules[:override])
338
+
339
+ return if rules[:without_accessors]
340
+
341
+ if override[:reader] && !method_defined_on_ancestor?(name) && !props.include?(name)
342
+ raise ArgumentError.new("You marked the getter for prop #{name.inspect} as `override`, but the method `#{name}` doesn't exist to be overridden.")
343
+ end
344
+
345
+ # Properly, we should also check whether `props[name]` is immutable, but the old code didn't either.
346
+ if !rules[:immutable] && override[:writer] && !method_defined_on_ancestor?("#{name}=".to_sym) && !props.include?(name)
347
+ raise ArgumentError.new("You marked the setter for prop #{name.inspect} as `override`, but the method `#{name}=` doesn't exist to be overridden.")
348
+ end
349
+ end
350
+
305
351
  # checked(:never) - Rules hash is expensive to check
306
352
  sig do
307
353
  params(
@@ -349,7 +395,7 @@ class T::Props::Decorator
349
395
  #
350
396
  if sensitivity_and_pii[:pii] && @class.is_a?(Class) && !T.unsafe(@class).contains_pii?
351
397
  raise ArgumentError.new(
352
- 'Cannot include a pii prop in a class that declares `contains_no_pii`'
398
+ "Cannot define pii prop `#{@class}##{name}` because `#{@class}` is `contains_no_pii`"
353
399
  )
354
400
  end
355
401
  end
@@ -364,8 +410,6 @@ class T::Props::Decorator
364
410
 
365
411
  # extra arbitrary metadata attached by the code defining this property
366
412
 
367
- validate_not_missing_sensitivity(name, rules)
368
-
369
413
  # for backcompat (the `:array` key is deprecated but because the name is
370
414
  # so generic it's really hard to be sure it's not being relied on anymore)
371
415
  if type.is_a?(T::Types::TypedArray)
@@ -381,6 +425,7 @@ class T::Props::Decorator
381
425
  rules[:setter_proc] = setter_proc
382
426
  rules[:value_validate_proc] = value_validate_proc
383
427
 
428
+ validate_overrides(name, rules)
384
429
  add_prop_definition(name, rules)
385
430
 
386
431
  # NB: using `without_accessors` doesn't make much sense unless you also define some other way to
@@ -392,7 +437,7 @@ class T::Props::Decorator
392
437
  end
393
438
 
394
439
  # checked(:never) - Rules hash is expensive to check
395
- sig {params(name: Symbol, rules: Rules).void.checked(:never)}
440
+ sig { params(name: Symbol, rules: Rules).void.checked(:never) }
396
441
  private def define_getter_and_setter(name, rules)
397
442
  T::Configuration.without_ruby_warnings do
398
443
  if !rules[:immutable]
@@ -405,6 +450,7 @@ class T::Props::Decorator
405
450
  # Fast path (~4x faster as of Ruby 2.6)
406
451
  @class.send(:define_method, "#{name}=", &rules.fetch(:setter_proc))
407
452
  end
453
+
408
454
  end
409
455
 
410
456
  if method(:prop_get).owner != T::Props::Decorator || rules.key?(:ifunset)
@@ -439,35 +485,6 @@ class T::Props::Decorator
439
485
  end
440
486
  end
441
487
 
442
- # checked(:never) - Rules hash is expensive to check
443
- sig {params(prop_name: Symbol, rules: Rules).void.checked(:never)}
444
- private def validate_not_missing_sensitivity(prop_name, rules)
445
- if rules[:sensitivity].nil?
446
- if rules[:redaction]
447
- T::Configuration.hard_assert_handler(
448
- "#{@class}##{prop_name} has a 'redaction:' annotation but no " \
449
- "'sensitivity:' annotation. This is probably wrong, because if a " \
450
- "prop needs redaction then it is probably sensitive. Add a " \
451
- "sensitivity annotation like 'sensitivity: Opus::Sensitivity::PII." \
452
- "whatever', or explicitly override this check with 'sensitivity: []'."
453
- )
454
- end
455
- # TODO(PRIVACYENG-982) Ideally we'd also check for 'password' and possibly
456
- # other terms, but this interacts badly with ProtoDefinedDocument because
457
- # the proto syntax currently can't declare "sensitivity: []"
458
- if /\bsecret\b/.match?(prop_name)
459
- T::Configuration.hard_assert_handler(
460
- "#{@class}##{prop_name} has the word 'secret' in its name, but no " \
461
- "'sensitivity:' annotation. This is probably wrong, because if a " \
462
- "prop is named 'secret' then it is probably sensitive. Add a " \
463
- "sensitivity annotation like 'sensitivity: Opus::Sensitivity::NonPII." \
464
- "security_token', or explicitly override this check with " \
465
- "'sensitivity: []'."
466
- )
467
- end
468
- end
469
- end
470
-
471
488
  # Create `#{prop_name}_redacted` method
472
489
  sig do
473
490
  params(
@@ -612,7 +629,7 @@ class T::Props::Decorator
612
629
  #
613
630
  # This gets called when a module or class that extends T::Props gets included, extended,
614
631
  # prepended, or inherited.
615
- sig {params(child: Module).void.checked(:never)}
632
+ sig { params(child: Module).void.checked(:never) }
616
633
  def model_inherited(child)
617
634
  child.extend(T::Props::ClassMethods)
618
635
  child = T.cast(child, T.all(Module, T::Props::ClassMethods))
@@ -627,7 +644,7 @@ class T::Props::Decorator
627
644
 
628
645
  props.each do |name, rules|
629
646
  copied_rules = rules.dup
630
- # NB: Calling `child.decorator` here is a timb bomb that's going to give someone a really bad
647
+ # NB: Calling `child.decorator` here is a time bomb that's going to give someone a really bad
631
648
  # time. Any class that defines props and also overrides the `decorator_class` method is going
632
649
  # to reach this line before its override take effect, turning it into a no-op.
633
650
  child.decorator.add_prop_definition(name, copied_rules)
@@ -656,19 +673,65 @@ class T::Props::Decorator
656
673
  end
657
674
  end
658
675
 
659
- sig {params(child: T.all(Module, T::Props::ClassMethods), prop: Symbol).returns(T::Boolean).checked(:never)}
676
+ sig(:final) do
677
+ params(key: Symbol, d: T.untyped, out: T::Hash[Symbol, {allow_incompatible: T::Boolean}])
678
+ .void
679
+ .checked(:never)
680
+ end
681
+ private def elaborate_override_entry(key, d, out)
682
+ # It's written this way so that `{reader: false}` will omit the entry for `reader` in the
683
+ # result entirely
684
+ case d[key]
685
+ when TrueClass
686
+ out[key] = {allow_incompatible: false}
687
+ when Hash
688
+ out[key] = {allow_incompatible: !!d[key][:allow_incompatible]}
689
+ end
690
+ end
691
+
692
+ sig(:final) do
693
+ params(name: Symbol, d: T.untyped)
694
+ .returns(T::Hash[Symbol, {allow_incompatible: T::Boolean}])
695
+ .checked(:never)
696
+ end
697
+ private def elaborate_override(name, d)
698
+ return OVERRIDE_TRUE if d == true
699
+ return OVERRIDE_READER if d == :reader
700
+ return OVERRIDE_WRITER if d == :writer
701
+ return OVERRIDE_EMPTY if d == false || d.nil?
702
+ unless d.is_a?(Hash)
703
+ raise ArgumentError.new("`override` only accepts `true`, `:reader`, `:writer`, or a Hash in prop #{@class.name}.#{name} (got #{d.class})")
704
+ end
705
+
706
+ # cwong: should we check for bad keys? `sig { override(not_real: true) }` on a normal function
707
+ # errors statically but not at runtime.
708
+
709
+ # XX cwong: this means {reader: false, allow_incompatible: true} will become {allow_incompatible: true},
710
+ # is that fine?
711
+ unless (allow_incompatible = d[:allow_incompatible]).nil?
712
+ return {reader: {allow_incompatible: !!allow_incompatible},
713
+ writer: {allow_incompatible: !!allow_incompatible}}.to_h
714
+ end
715
+
716
+ result = {}
717
+ elaborate_override_entry(:reader, d, result)
718
+ elaborate_override_entry(:writer, d, result)
719
+ result
720
+ end
721
+
722
+ sig { params(child: T.all(Module, T::Props::ClassMethods), prop: Symbol).returns(T::Boolean).checked(:never) }
660
723
  private def clobber_getter?(child, prop)
661
724
  !!(child.decorator.method(:prop_get).owner != method(:prop_get).owner &&
662
725
  child.instance_method(prop).source_location&.first == __FILE__)
663
726
  end
664
727
 
665
- sig {params(child: T.all(Module, T::Props::ClassMethods), prop: Symbol).returns(T::Boolean).checked(:never)}
728
+ sig { params(child: T.all(Module, T::Props::ClassMethods), prop: Symbol).returns(T::Boolean).checked(:never) }
666
729
  private def clobber_setter?(child, prop)
667
730
  !!(child.decorator.method(:prop_set).owner != method(:prop_set).owner &&
668
731
  child.instance_method("#{prop}=").source_location&.first == __FILE__)
669
732
  end
670
733
 
671
- sig {params(mod: Module).void.checked(:never)}
734
+ sig { params(mod: Module).void.checked(:never) }
672
735
  def plugin(mod)
673
736
  decorated_class.plugins << mod
674
737
  T::Props::Plugin::Private.apply_class_methods(mod, decorated_class)
@@ -159,7 +159,7 @@ module T::Props
159
159
  assert_equal(:resbody, rescue_body.type)
160
160
  exceptions, assignment, handler = rescue_body.children
161
161
  assert_equal(:array, exceptions.type)
162
- exceptions.children.each {|c| assert_equal(:const, c.type)}
162
+ exceptions.children.each { |c| assert_equal(:const, c.type) }
163
163
  assert_equal(:lvasgn, assignment.type)
164
164
  assert_equal([:e], assignment.children)
165
165
 
@@ -169,7 +169,7 @@ module T::Props
169
169
  receiver, method, *args = deserialization_error.children
170
170
  assert_equal(nil, receiver)
171
171
  assert_equal(:raise_deserialization_error, method)
172
- args.each {|a| validate_lack_of_side_effects(a, whitelisted_methods_for_deserialize)}
172
+ args.each { |a| validate_lack_of_side_effects(a, whitelisted_methods_for_deserialize) }
173
173
 
174
174
  validate_lack_of_side_effects(val_return, whitelisted_methods_for_deserialize)
175
175
  else
@@ -222,12 +222,12 @@ module T::Props
222
222
  # Primitives & self are ok
223
223
  when :lvar, :arg, :ivar
224
224
  # Reading local & instance variables & arguments is ok
225
- unless node.children.all? {|c| c.is_a?(Symbol)}
225
+ unless node.children.all? { |c| c.is_a?(Symbol) }
226
226
  raise ValidationError.new("Unexpected child for #{node.type}: #{node.inspect}")
227
227
  end
228
228
  when :args, :mlhs, :block, :begin, :if
229
229
  # Blocks etc are read-only if their contents are read-only
230
- node.children.each {|c| validate_lack_of_side_effects(c, whitelisted_methods_by_receiver_type) if c}
230
+ node.children.each { |c| validate_lack_of_side_effects(c, whitelisted_methods_by_receiver_type) if c }
231
231
  when :send
232
232
  # Sends are riskier so check a whitelist
233
233
  receiver, method, *args = node.children
@@ -30,30 +30,30 @@ module T::Props
30
30
  #
31
31
  # Note it does _not_ prevent explicit calls to `eagerly_define_lazy_methods!`
32
32
  # from working.
33
- sig {void}
33
+ sig { void }
34
34
  def self.disable_lazy_evaluation!
35
35
  @lazy_evaluation_disabled ||= true
36
36
  end
37
37
 
38
- sig {returns(T::Boolean)}
38
+ sig { returns(T::Boolean) }
39
39
  def self.lazy_evaluation_enabled?
40
- !defined?(@lazy_evaluation_disabled) || !@lazy_evaluation_disabled
40
+ !@lazy_evaluation_disabled
41
41
  end
42
42
 
43
43
  module DecoratorMethods
44
44
  extend T::Sig
45
45
 
46
- sig {returns(T::Hash[Symbol, T.proc.returns(String)]).checked(:never)}
46
+ sig { returns(T::Hash[Symbol, T.proc.returns(String)]).checked(:never) }
47
47
  private def lazily_defined_methods
48
48
  @lazily_defined_methods ||= {}
49
49
  end
50
50
 
51
- sig {returns(T::Hash[Symbol, T.untyped]).checked(:never)}
51
+ sig { returns(T::Hash[Symbol, T.untyped]).checked(:never) }
52
52
  private def lazily_defined_vm_methods
53
53
  @lazily_defined_vm_methods ||= {}
54
54
  end
55
55
 
56
- sig {params(name: Symbol).void}
56
+ sig { params(name: Symbol).void }
57
57
  private def eval_lazily_defined_method!(name)
58
58
  if !HasLazilySpecializedMethods.lazy_evaluation_enabled?
59
59
  raise SourceEvaluationDisabled.new
@@ -68,7 +68,7 @@ module T::Props
68
68
  cls.send(:private, name)
69
69
  end
70
70
 
71
- sig {params(name: Symbol).void}
71
+ sig { params(name: Symbol).void }
72
72
  private def eval_lazily_defined_vm_method!(name)
73
73
  if !HasLazilySpecializedMethods.lazy_evaluation_enabled?
74
74
  raise SourceEvaluationDisabled.new
@@ -80,7 +80,7 @@ module T::Props
80
80
  cls.send(:private, name)
81
81
  end
82
82
 
83
- sig {params(name: Symbol, blk: T.proc.returns(String)).void}
83
+ sig { params(name: Symbol, blk: T.proc.returns(String)).void }
84
84
  private def enqueue_lazy_method_definition!(name, &blk)
85
85
  lazily_defined_methods[name] = blk
86
86
 
@@ -100,7 +100,7 @@ module T::Props
100
100
  cls.send(:private, name)
101
101
  end
102
102
 
103
- sig {params(name: Symbol, blk: T.untyped).void}
103
+ sig { params(name: Symbol, blk: T.untyped).void }
104
104
  private def enqueue_lazy_vm_method_definition!(name, &blk)
105
105
  lazily_defined_vm_methods[name] = blk
106
106
 
@@ -115,7 +115,7 @@ module T::Props
115
115
  cls.send(:private, name)
116
116
  end
117
117
 
118
- sig {void}
118
+ sig { void }
119
119
  def eagerly_define_lazy_methods!
120
120
  return if lazily_defined_methods.empty?
121
121
 
@@ -125,18 +125,18 @@ module T::Props
125
125
 
126
126
  cls = decorated_class
127
127
  cls.class_eval(source)
128
- lazily_defined_methods.each_key {|name| cls.send(:private, name)}
128
+ lazily_defined_methods.each_key { |name| cls.send(:private, name) }
129
129
  lazily_defined_methods.clear
130
130
  end
131
131
 
132
- sig {void}
132
+ sig { void }
133
133
  def eagerly_define_lazy_vm_methods!
134
134
  return if lazily_defined_vm_methods.empty?
135
135
 
136
136
  lazily_defined_vm_methods.values.map(&:call)
137
137
 
138
138
  cls = decorated_class
139
- lazily_defined_vm_methods.each_key {|name| cls.send(:private, name)}
139
+ lazily_defined_vm_methods.each_key { |name| cls.send(:private, name) }
140
140
  lazily_defined_vm_methods.clear
141
141
  end
142
142
  end
@@ -43,11 +43,11 @@ module T::Props::Optional::DecoratorMethods
43
43
  end
44
44
 
45
45
  # checked(:never) - O(runtime object construction)
46
- sig {returns(T::Hash[Symbol, T::Props::Private::ApplyDefault]).checked(:never)}
46
+ sig { returns(T::Hash[Symbol, T::Props::Private::ApplyDefault]).checked(:never) }
47
47
  attr_reader :props_with_defaults
48
48
 
49
49
  # checked(:never) - O(runtime object construction)
50
- sig {returns(T::Hash[Symbol, T::Props::Private::SetterFactory::SetterProc]).checked(:never)}
50
+ sig { returns(T::Hash[Symbol, T::Props::Private::SetterFactory::SetterProc]).checked(:never) }
51
51
  attr_reader :props_without_defaults
52
52
 
53
53
  def add_prop_definition(prop, rules)