sorbet-runtime 0.5.10439 → 0.5.11120
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sorbet-runtime.rb +7 -1
- data/lib/types/_types.rb +57 -3
- data/lib/types/compatibility_patches.rb +4 -2
- data/lib/types/enum.rb +6 -1
- data/lib/types/generic.rb +2 -0
- data/lib/types/private/abstract/declare.rb +10 -9
- data/lib/types/private/casts.rb +4 -1
- data/lib/types/private/class_utils.rb +13 -6
- data/lib/types/private/methods/_methods.rb +37 -12
- data/lib/types/private/methods/call_validation.rb +104 -5
- data/lib/types/private/methods/call_validation_2_6.rb +68 -60
- data/lib/types/private/methods/call_validation_2_7.rb +68 -60
- data/lib/types/private/methods/decl_builder.rb +21 -6
- data/lib/types/private/methods/signature.rb +63 -38
- data/lib/types/private/methods/signature_validation.rb +68 -6
- data/lib/types/private/runtime_levels.rb +19 -0
- data/lib/types/private/types/not_typed.rb +2 -0
- data/lib/types/private/types/simple_pair_union.rb +55 -0
- data/lib/types/private/types/void.rb +29 -23
- data/lib/types/props/_props.rb +2 -2
- data/lib/types/props/custom_type.rb +2 -2
- data/lib/types/props/decorator.rb +41 -36
- data/lib/types/props/has_lazily_specialized_methods.rb +2 -2
- data/lib/types/props/pretty_printable.rb +45 -83
- data/lib/types/props/private/setter_factory.rb +1 -1
- data/lib/types/props/serializable.rb +17 -9
- data/lib/types/props/type_validation.rb +5 -2
- data/lib/types/struct.rb +2 -2
- data/lib/types/types/anything.rb +31 -0
- data/lib/types/types/base.rb +15 -1
- data/lib/types/types/class_of.rb +11 -0
- data/lib/types/types/enum.rb +1 -1
- data/lib/types/types/fixed_array.rb +13 -0
- data/lib/types/types/fixed_hash.rb +22 -0
- data/lib/types/types/intersection.rb +1 -1
- data/lib/types/types/noreturn.rb +0 -1
- data/lib/types/types/simple.rb +27 -4
- data/lib/types/types/type_parameter.rb +19 -0
- data/lib/types/types/typed_array.rb +29 -0
- data/lib/types/types/typed_class.rb +85 -0
- data/lib/types/types/typed_enumerable.rb +7 -0
- data/lib/types/types/typed_enumerator_chain.rb +41 -0
- data/lib/types/types/union.rb +50 -14
- data/lib/types/utils.rb +41 -33
- metadata +14 -10
@@ -6,14 +6,63 @@ module T::Private::Methods::SignatureValidation
|
|
6
6
|
Modes = Methods::Modes
|
7
7
|
|
8
8
|
def self.validate(signature)
|
9
|
+
# Constructors in any language are always a bit weird: they're called in a
|
10
|
+
# static context, but their bodies are implemented by instance methods. So
|
11
|
+
# a mix of the rules that apply to instance methods and class methods
|
12
|
+
# apply.
|
13
|
+
#
|
14
|
+
# In languages like Java and Scala, static methods/companion object methods
|
15
|
+
# are never inherited. (In Java it almost looks like you can inherit them,
|
16
|
+
# because `Child.static_parent_method` works, but this method is simply
|
17
|
+
# resolved statically to `Parent.static_parent_method`). Even though most
|
18
|
+
# instance methods overrides have variance checking done, constructors are
|
19
|
+
# not treated like this, because static methods are never
|
20
|
+
# inherited/overridden, and the constructor can only ever be called
|
21
|
+
# indirectly by way of the static method. (Note: this is only a mental
|
22
|
+
# model--there's not actually a static method for the constructor in Java,
|
23
|
+
# there's an `invokespecial` JVM instruction that handles this).
|
24
|
+
#
|
25
|
+
# But Ruby is not like Java: singleton class methods in Ruby *are*
|
26
|
+
# inherited, unlike static methods in Java. In fact, this is similar to how
|
27
|
+
# JavaScript works. TypeScript simply then sidesteps the issue with
|
28
|
+
# structural typing: `typeof Parent` is not compatible with `typeof Child`
|
29
|
+
# if their constructors are different. (In a nominal type system, simply
|
30
|
+
# having Child descend from Parent should be the only factor in determining
|
31
|
+
# whether those types are compatible).
|
32
|
+
#
|
33
|
+
# Flow has nominal subtyping for classes. When overriding (static and
|
34
|
+
# instance) methods in a child class, the overrides must satisfy variance
|
35
|
+
# constraints. But it still carves out an exception for constructors,
|
36
|
+
# because then literally every class would have to have the same
|
37
|
+
# constructor. This is simply unsound. Hack does a similar thing--static
|
38
|
+
# method overrides are checked, but not constructors. Though what Hack
|
39
|
+
# *does* have is a way to opt into override checking for constructors with
|
40
|
+
# a special annotation.
|
41
|
+
#
|
42
|
+
# It turns out, Sorbet already has this special annotation: either
|
43
|
+
# `abstract` or `overridable`. At time of writing, *no* static override
|
44
|
+
# checking happens unless marked with these keywords (though at runtime, it
|
45
|
+
# always happens). Getting the static system to parity with the runtime by
|
46
|
+
# always checking overrides would be a great place to get to one day, but
|
47
|
+
# for now we can take advantage of it by only doing override checks for
|
48
|
+
# constructors if they've opted in.
|
49
|
+
#
|
50
|
+
# (When we get around to more widely checking overrides statically, we will
|
51
|
+
# need to build a matching special case for constructors statically.)
|
52
|
+
#
|
53
|
+
# Note that this breaks with tradition: normally, constructors are not
|
54
|
+
# allowed to be abstract. But that's kind of a side-effect of everything
|
55
|
+
# above: in Java/Scala, singleton class methods are never abstract because
|
56
|
+
# they're not inherited, and this extends to constructors. TypeScript
|
57
|
+
# simply rejects `new klass()` entirely if `klass` is
|
58
|
+
# `typeof AbstractClass`, requiring instead that you write
|
59
|
+
# `{ new(): AbstractClass }`. We may want to consider building some
|
60
|
+
# analogue to `T.class_of` in the future that works like this `{new():
|
61
|
+
# ...}` type.
|
9
62
|
if signature.method_name == :initialize && signature.method.owner.is_a?(Class)
|
10
|
-
|
11
|
-
|
12
|
-
# methods (this is consistent with how they're treated in other languages, e.g. Java)
|
13
|
-
if signature.mode != Modes.standard
|
14
|
-
raise "`initialize` should not use `.abstract` or `.implementation` or any other inheritance modifiers."
|
63
|
+
if signature.mode == Modes.standard
|
64
|
+
return
|
15
65
|
end
|
16
|
-
return
|
17
66
|
end
|
18
67
|
|
19
68
|
super_method = signature.method.super_method
|
@@ -59,6 +108,19 @@ module T::Private::Methods::SignatureValidation
|
|
59
108
|
case signature.mode
|
60
109
|
when *Modes::OVERRIDE_MODES
|
61
110
|
# Peaceful
|
111
|
+
when Modes.abstract
|
112
|
+
# Either the parent method is abstract, or it's not.
|
113
|
+
#
|
114
|
+
# If it's abstract, we want to allow overriding abstract with abstract to
|
115
|
+
# possibly narrow the type or provide more specific documentation.
|
116
|
+
#
|
117
|
+
# If it's not, then marking this method `abstract` will silently be a no-op.
|
118
|
+
# That's bad and we probably want to report an error, but fixing that
|
119
|
+
# will have to be a separate fix (that bad behavior predates this current
|
120
|
+
# comment, introduced when we fixed the abstract/abstract case).
|
121
|
+
#
|
122
|
+
# Therefore:
|
123
|
+
# Peaceful (mostly)
|
62
124
|
when *Modes::NON_OVERRIDE_MODES
|
63
125
|
if super_signature.mode == Modes.standard
|
64
126
|
# Peaceful
|
@@ -53,10 +53,29 @@ module T::Private::RuntimeLevels
|
|
53
53
|
if @has_read_default_checked_level
|
54
54
|
raise "Set the default checked level earlier. There are already some methods whose sig blocks have evaluated which would not be affected by the new default."
|
55
55
|
end
|
56
|
+
if !LEVELS.include?(default_checked_level)
|
57
|
+
raise "Invalid `checked` level '#{default_checked_level}'. Use one of: #{LEVELS}."
|
58
|
+
end
|
59
|
+
|
56
60
|
@default_checked_level = default_checked_level
|
57
61
|
end
|
58
62
|
|
59
63
|
def self._toggle_checking_tests(checked)
|
60
64
|
@check_tests = checked
|
61
65
|
end
|
66
|
+
|
67
|
+
private_class_method def self.set_enable_checking_in_tests_from_environment
|
68
|
+
if ENV['SORBET_RUNTIME_ENABLE_CHECKING_IN_TESTS']
|
69
|
+
enable_checking_in_tests
|
70
|
+
end
|
71
|
+
end
|
72
|
+
set_enable_checking_in_tests_from_environment
|
73
|
+
|
74
|
+
private_class_method def self.set_default_checked_level_from_environment
|
75
|
+
level = ENV['SORBET_RUNTIME_DEFAULT_CHECKED_LEVEL']
|
76
|
+
if level
|
77
|
+
self.default_checked_level = level.to_sym
|
78
|
+
end
|
79
|
+
end
|
80
|
+
set_default_checked_level_from_environment
|
62
81
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# Specialization of Union for the common case of the union of two simple types.
|
5
|
+
#
|
6
|
+
# This covers e.g. T.nilable(SomeModule), T.any(Integer, Float), and T::Boolean.
|
7
|
+
class T::Private::Types::SimplePairUnion < T::Types::Union
|
8
|
+
class DuplicateType < RuntimeError; end
|
9
|
+
|
10
|
+
# @param type_a [T::Types::Simple]
|
11
|
+
# @param type_b [T::Types::Simple]
|
12
|
+
def initialize(type_a, type_b)
|
13
|
+
if type_a == type_b
|
14
|
+
raise DuplicateType.new("#{type_a} == #{type_b}")
|
15
|
+
end
|
16
|
+
|
17
|
+
@raw_a = type_a.raw_type
|
18
|
+
@raw_b = type_b.raw_type
|
19
|
+
end
|
20
|
+
|
21
|
+
# @override Union
|
22
|
+
def recursively_valid?(obj)
|
23
|
+
obj.is_a?(@raw_a) || obj.is_a?(@raw_b)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @override Union
|
27
|
+
def valid?(obj)
|
28
|
+
obj.is_a?(@raw_a) || obj.is_a?(@raw_b)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @override Union
|
32
|
+
def types
|
33
|
+
# We reconstruct the simple types rather than just storing them because
|
34
|
+
# (1) this is normally not a hot path and (2) we want to keep the instance
|
35
|
+
# variable count <= 3 so that we can fit in a 40 byte heap entry along
|
36
|
+
# with object headers.
|
37
|
+
@types ||= [
|
38
|
+
T::Types::Simple::Private::Pool.type_for_module(@raw_a),
|
39
|
+
T::Types::Simple::Private::Pool.type_for_module(@raw_b),
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
# overrides Union
|
44
|
+
def unwrap_nilable
|
45
|
+
a_nil = @raw_a.equal?(NilClass)
|
46
|
+
b_nil = @raw_b.equal?(NilClass)
|
47
|
+
if a_nil
|
48
|
+
return types[1]
|
49
|
+
end
|
50
|
+
if b_nil
|
51
|
+
return types[0]
|
52
|
+
end
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
@@ -3,32 +3,38 @@
|
|
3
3
|
|
4
4
|
# A marking class for when methods return void.
|
5
5
|
# Should never appear in types directly.
|
6
|
-
|
7
|
-
|
6
|
+
module T::Private::Types
|
7
|
+
class Void < T::Types::Base
|
8
|
+
ERROR_MESSAGE = "Validation is being done on an `Void`. Please report this bug at https://github.com/sorbet/sorbet/issues"
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
# The actual return value of `.void` methods.
|
11
|
+
#
|
12
|
+
# Uses `module VOID` because this gives it a readable name when someone
|
13
|
+
# examines it in Pry or with `#inspect` like:
|
14
|
+
#
|
15
|
+
# T::Private::Types::Void::VOID
|
16
|
+
#
|
17
|
+
module VOID
|
18
|
+
freeze
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
# overrides Base
|
22
|
+
def name
|
23
|
+
"<VOID>"
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
# overrides Base
|
27
|
+
def valid?(obj)
|
28
|
+
raise ERROR_MESSAGE
|
29
|
+
end
|
30
|
+
|
31
|
+
# overrides Base
|
32
|
+
private def subtype_of_single?(other)
|
33
|
+
raise ERROR_MESSAGE
|
34
|
+
end
|
29
35
|
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
module Private
|
37
|
+
INSTANCE = Void.new.freeze
|
38
|
+
end
|
33
39
|
end
|
34
40
|
end
|
data/lib/types/props/_props.rb
CHANGED
@@ -139,9 +139,9 @@ module T::Props
|
|
139
139
|
end
|
140
140
|
|
141
141
|
if cls_or_args.is_a?(Hash)
|
142
|
-
self.prop(name, cls_or_args.merge(immutable: true))
|
142
|
+
self.prop(name, **cls_or_args.merge(immutable: true))
|
143
143
|
else
|
144
|
-
self.prop(name, cls_or_args, args.merge(immutable: true))
|
144
|
+
self.prop(name, cls_or_args, **args.merge(immutable: true))
|
145
145
|
end
|
146
146
|
end
|
147
147
|
|
@@ -57,12 +57,12 @@ module T::Props
|
|
57
57
|
raise 'Please use "extend", not "include" to attach this module'
|
58
58
|
end
|
59
59
|
|
60
|
-
sig(:final) {params(val:
|
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
|
64
64
|
# are all classes.
|
65
|
-
klass =
|
65
|
+
klass = val.class
|
66
66
|
until klass.nil?
|
67
67
|
return true if T::Configuration.scalar_types.include?(klass.to_s)
|
68
68
|
klass = klass.superclass
|
@@ -17,7 +17,7 @@ class T::Props::Decorator
|
|
17
17
|
|
18
18
|
class NoRulesError < StandardError; end
|
19
19
|
|
20
|
-
EMPTY_PROPS = T.let({}.freeze, T::Hash[Symbol, Rules])
|
20
|
+
EMPTY_PROPS = T.let({}.freeze, T::Hash[Symbol, Rules], checked: false)
|
21
21
|
private_constant :EMPTY_PROPS
|
22
22
|
|
23
23
|
sig {params(klass: T.untyped).void.checked(:never)}
|
@@ -26,7 +26,7 @@ class T::Props::Decorator
|
|
26
26
|
@class.plugins.each do |mod|
|
27
27
|
T::Props::Plugin::Private.apply_decorator_methods(mod, self)
|
28
28
|
end
|
29
|
-
@props = T.let(EMPTY_PROPS, T::Hash[Symbol, Rules])
|
29
|
+
@props = T.let(EMPTY_PROPS, T::Hash[Symbol, Rules], checked: false)
|
30
30
|
end
|
31
31
|
|
32
32
|
# checked(:never) - O(prop accesses)
|
@@ -50,9 +50,9 @@ class T::Props::Decorator
|
|
50
50
|
override = rules.delete(:override)
|
51
51
|
|
52
52
|
if props.include?(prop) && !override
|
53
|
-
raise ArgumentError.new("Attempted to redefine prop #{prop.inspect} that's already defined without specifying :override => true: #{prop_rules(prop)}")
|
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
54
|
elsif !props.include?(prop) && override
|
55
|
-
raise ArgumentError.new("Attempted to override a prop #{prop.inspect} that doesn't already exist")
|
55
|
+
raise ArgumentError.new("Attempted to override a prop #{prop.inspect} on class #{@class} that doesn't already exist")
|
56
56
|
end
|
57
57
|
|
58
58
|
@props = @props.merge(prop => rules.freeze).freeze
|
@@ -79,7 +79,7 @@ class T::Props::Decorator
|
|
79
79
|
extra
|
80
80
|
setter_validate
|
81
81
|
_tnilable
|
82
|
-
].
|
82
|
+
].to_h {|k| [k, true]}.freeze, T::Hash[Symbol, T::Boolean], checked: false)
|
83
83
|
private_constant :VALID_RULE_KEYS
|
84
84
|
|
85
85
|
sig {params(key: Symbol).returns(T::Boolean).checked(:never)}
|
@@ -205,7 +205,7 @@ class T::Props::Decorator
|
|
205
205
|
end
|
206
206
|
|
207
207
|
# TODO: we should really be checking all the methods on `cls`, not just Object
|
208
|
-
BANNED_METHOD_NAMES = T.let(Object.instance_methods.each_with_object({}) {|x, acc| acc[x] = true}.freeze, T::Hash[Symbol, TrueClass])
|
208
|
+
BANNED_METHOD_NAMES = T.let(Object.instance_methods.each_with_object({}) {|x, acc| acc[x] = true}.freeze, T::Hash[Symbol, TrueClass], checked: false)
|
209
209
|
|
210
210
|
# checked(:never) - Rules hash is expensive to check
|
211
211
|
sig do
|
@@ -247,10 +247,10 @@ class T::Props::Decorator
|
|
247
247
|
nil
|
248
248
|
end
|
249
249
|
|
250
|
-
SAFE_NAME = T.let(/\A[A-Za-z_][A-Za-z0-9_-]*\z/.freeze, Regexp)
|
250
|
+
SAFE_NAME = T.let(/\A[A-Za-z_][A-Za-z0-9_-]*\z/.freeze, Regexp, checked: false)
|
251
251
|
|
252
252
|
# Used to validate both prop names and serialized forms
|
253
|
-
sig {params(name: T.any(Symbol, String)).void}
|
253
|
+
sig {params(name: T.any(Symbol, String)).void.checked(:never)}
|
254
254
|
private def validate_prop_name(name)
|
255
255
|
if !name.match?(SAFE_NAME)
|
256
256
|
raise ArgumentError.new("Invalid prop name in #{@class.name}: #{name}")
|
@@ -258,7 +258,7 @@ class T::Props::Decorator
|
|
258
258
|
end
|
259
259
|
|
260
260
|
# This converts the type from a T::Type to a regular old ruby class.
|
261
|
-
sig {params(type: T::Types::Base).returns(Module)}
|
261
|
+
sig {params(type: T::Types::Base).returns(Module).checked(:never)}
|
262
262
|
private def convert_type_to_class(type)
|
263
263
|
case type
|
264
264
|
when T::Types::TypedArray, T::Types::FixedArray
|
@@ -300,7 +300,9 @@ class T::Props::Decorator
|
|
300
300
|
.checked(:never)
|
301
301
|
end
|
302
302
|
private def prop_nilable?(cls, rules)
|
303
|
-
T::Utils::
|
303
|
+
# NB: `prop` and `const` do not `T::Utils::coerce the type of the prop if it is a `Module`,
|
304
|
+
# hence the bare `NilClass` check.
|
305
|
+
T::Utils::Nilable.is_union_with_nilclass(cls) || ((cls == T.untyped || cls == NilClass) && rules.key?(:default) && rules[:default].nil?)
|
304
306
|
end
|
305
307
|
|
306
308
|
# checked(:never) - Rules hash is expensive to check
|
@@ -336,34 +338,34 @@ class T::Props::Decorator
|
|
336
338
|
# Retrive the possible underlying object with T.nilable.
|
337
339
|
type = T::Utils::Nilable.get_underlying_type(type)
|
338
340
|
|
339
|
-
|
340
|
-
|
341
|
-
if
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
341
|
+
rules_sensitivity = rules[:sensitivity]
|
342
|
+
sensitivity_and_pii = {sensitivity: rules_sensitivity}
|
343
|
+
if !rules_sensitivity.nil?
|
344
|
+
normalize = T::Configuration.normalize_sensitivity_and_pii_handler
|
345
|
+
if normalize
|
346
|
+
sensitivity_and_pii = normalize.call(sensitivity_and_pii)
|
347
|
+
|
348
|
+
# We check for Class so this is only applied on concrete
|
349
|
+
# documents/models; We allow mixins containing props to not
|
350
|
+
# specify their PII nature, as long as every class into which they
|
351
|
+
# are ultimately included does.
|
352
|
+
#
|
353
|
+
if sensitivity_and_pii[:pii] && @class.is_a?(Class) && !T.unsafe(@class).contains_pii?
|
354
|
+
raise ArgumentError.new(
|
355
|
+
'Cannot include a pii prop in a class that declares `contains_no_pii`'
|
356
|
+
)
|
357
|
+
end
|
353
358
|
end
|
354
359
|
end
|
355
360
|
|
356
|
-
rules =
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
# extra arbitrary metadata attached by the code defining this property
|
365
|
-
extra: rules[:extra]&.freeze,
|
366
|
-
)
|
361
|
+
rules[:type] = type
|
362
|
+
rules[:type_object] = type_object
|
363
|
+
rules[:accessor_key] = "@#{name}".to_sym
|
364
|
+
rules[:sensitivity] = sensitivity_and_pii[:sensitivity]
|
365
|
+
rules[:pii] = sensitivity_and_pii[:pii]
|
366
|
+
rules[:extra] = rules[:extra]&.freeze
|
367
|
+
|
368
|
+
# extra arbitrary metadata attached by the code defining this property
|
367
369
|
|
368
370
|
validate_not_missing_sensitivity(name, rules)
|
369
371
|
|
@@ -417,6 +419,7 @@ class T::Props::Decorator
|
|
417
419
|
sig do
|
418
420
|
params(type: PropTypeOrClass, enum: T.untyped)
|
419
421
|
.returns(T::Types::Base)
|
422
|
+
.checked(:never)
|
420
423
|
end
|
421
424
|
private def smart_coerce(type, enum:)
|
422
425
|
# Backwards compatibility for pre-T::Types style
|
@@ -469,6 +472,7 @@ class T::Props::Decorator
|
|
469
472
|
redaction: T.untyped,
|
470
473
|
)
|
471
474
|
.void
|
475
|
+
.checked(:never)
|
472
476
|
end
|
473
477
|
private def handle_redaction_option(prop_name, redaction)
|
474
478
|
redacted_method = "#{prop_name}_redacted"
|
@@ -490,6 +494,7 @@ class T::Props::Decorator
|
|
490
494
|
valid_type_msg: String,
|
491
495
|
)
|
492
496
|
.void
|
497
|
+
.checked(:never)
|
493
498
|
end
|
494
499
|
private def validate_foreign_option(option_sym, foreign, valid_type_msg:)
|
495
500
|
if foreign.is_a?(Symbol) || foreign.is_a?(String)
|
@@ -521,8 +526,8 @@ class T::Props::Decorator
|
|
521
526
|
# here, but we're baking in `allow_direct_mutation` since we
|
522
527
|
# *haven't* allowed additional options in the past and want to
|
523
528
|
# default to keeping this interface narrow.
|
529
|
+
foreign = T.let(foreign, T.untyped, checked: false)
|
524
530
|
@class.send(:define_method, fk_method) do |allow_direct_mutation: nil|
|
525
|
-
foreign = T.let(foreign, T.untyped)
|
526
531
|
if foreign.is_a?(Proc)
|
527
532
|
resolved_foreign = foreign.call
|
528
533
|
if !resolved_foreign.respond_to?(:load)
|
@@ -83,7 +83,7 @@ module T::Props
|
|
83
83
|
lazily_defined_methods[name] = blk
|
84
84
|
|
85
85
|
cls = decorated_class
|
86
|
-
if cls.method_defined?(name)
|
86
|
+
if cls.method_defined?(name) || cls.private_method_defined?(name)
|
87
87
|
# Ruby does not emit "method redefined" warnings for aliased methods
|
88
88
|
# (more robust than undef_method that would create a small window in which the method doesn't exist)
|
89
89
|
cls.send(:alias_method, name, name)
|
@@ -117,7 +117,7 @@ module T::Props
|
|
117
117
|
def eagerly_define_lazy_methods!
|
118
118
|
return if lazily_defined_methods.empty?
|
119
119
|
|
120
|
-
source = lazily_defined_methods.values.map(&:call).map(&:to_s).join("\n\n")
|
120
|
+
source = "# frozen_string_literal: true\n" + lazily_defined_methods.values.map(&:call).map(&:to_s).join("\n\n")
|
121
121
|
|
122
122
|
cls = decorated_class
|
123
123
|
cls.class_eval(source)
|
@@ -1,18 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# typed: true
|
3
|
+
require 'pp'
|
3
4
|
|
4
5
|
module T::Props::PrettyPrintable
|
5
6
|
include T::Props::Plugin
|
6
7
|
|
7
|
-
#
|
8
|
+
# Override the PP gem with something that's similar, but gives us a hook to do redaction and customization
|
9
|
+
def pretty_print(pp)
|
10
|
+
clazz = T.unsafe(T.cast(self, Object).class).decorator
|
11
|
+
multiline = pp.is_a?(PP)
|
12
|
+
pp.group(1, "<#{clazz.inspect_class_with_decoration(self)}", ">") do
|
13
|
+
clazz.all_props.sort.each do |prop|
|
14
|
+
pp.breakable
|
15
|
+
val = clazz.get(self, prop)
|
16
|
+
rules = clazz.prop_rules(prop)
|
17
|
+
pp.text("#{prop}=")
|
18
|
+
if (custom_inspect = rules[:inspect])
|
19
|
+
inspected = if T::Utils.arity(custom_inspect) == 1
|
20
|
+
custom_inspect.call(val)
|
21
|
+
else
|
22
|
+
custom_inspect.call(val, {multiline: multiline})
|
23
|
+
end
|
24
|
+
pp.text(inspected.nil? ? "nil" : inspected)
|
25
|
+
elsif rules[:sensitivity] && !rules[:sensitivity].empty? && !val.nil?
|
26
|
+
pp.text("<REDACTED #{rules[:sensitivity].join(', ')}>")
|
27
|
+
else
|
28
|
+
val.pretty_print(pp)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
clazz.pretty_print_extra(self, pp)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return a string representation of this object and all of its props in a single line
|
8
36
|
def inspect
|
9
|
-
|
37
|
+
string = +""
|
38
|
+
PP.singleline_pp(self, string)
|
39
|
+
string
|
10
40
|
end
|
11
41
|
|
12
|
-
#
|
13
|
-
# to do redaction
|
42
|
+
# Return a pretty string representation of this object and all of its props
|
14
43
|
def pretty_inspect
|
15
|
-
|
44
|
+
string = +""
|
45
|
+
PP.pp(self, string)
|
46
|
+
string
|
16
47
|
end
|
17
48
|
|
18
49
|
module DecoratorMethods
|
@@ -23,85 +54,16 @@ module T::Props::PrettyPrintable
|
|
23
54
|
super || key == :inspect
|
24
55
|
end
|
25
56
|
|
26
|
-
|
27
|
-
|
28
|
-
|
57
|
+
# Overridable method to specify how the first part of a `pretty_print`d object's class should look like
|
58
|
+
# NOTE: This is just to support Stripe's `PrettyPrintableModel` case, and not recommended to be overriden
|
59
|
+
sig {params(instance: T::Props::PrettyPrintable).returns(String)}
|
60
|
+
def inspect_class_with_decoration(instance)
|
61
|
+
T.unsafe(instance).class.to_s
|
29
62
|
end
|
30
|
-
def inspect_instance(instance, multiline: false, indent: ' ')
|
31
|
-
components =
|
32
|
-
inspect_instance_components(
|
33
|
-
instance,
|
34
|
-
multiline: multiline,
|
35
|
-
indent: indent
|
36
|
-
)
|
37
|
-
.reject(&:empty?)
|
38
|
-
|
39
|
-
# Not using #<> here as that makes pry highlight these objects
|
40
|
-
# as if they were all comments, whereas this makes them look
|
41
|
-
# like the structured thing they are.
|
42
|
-
if multiline
|
43
|
-
"#{components[0]}:\n" + T.must(components[1..-1]).join("\n")
|
44
|
-
else
|
45
|
-
"<#{components.join(' ')}>"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
sig do
|
50
|
-
params(instance: T::Props::PrettyPrintable, multiline: T::Boolean, indent: String)
|
51
|
-
.returns(T::Array[String])
|
52
|
-
end
|
53
|
-
private def inspect_instance_components(instance, multiline:, indent:)
|
54
|
-
pretty_props = T.unsafe(self).all_props.map do |prop|
|
55
|
-
[prop, inspect_prop_value(instance, prop, multiline: multiline, indent: indent)]
|
56
|
-
end
|
57
|
-
|
58
|
-
joined_props = join_props_with_pretty_values(
|
59
|
-
pretty_props,
|
60
|
-
multiline: multiline,
|
61
|
-
indent: indent
|
62
|
-
)
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
sig do
|
71
|
-
params(instance: T::Props::PrettyPrintable, prop: Symbol, multiline: T::Boolean, indent: String)
|
72
|
-
.returns(String)
|
73
|
-
.checked(:never)
|
74
|
-
end
|
75
|
-
private def inspect_prop_value(instance, prop, multiline:, indent:)
|
76
|
-
val = T.unsafe(self).get(instance, prop)
|
77
|
-
rules = T.unsafe(self).prop_rules(prop)
|
78
|
-
if (custom_inspect = rules[:inspect])
|
79
|
-
if T::Utils.arity(custom_inspect) == 1
|
80
|
-
custom_inspect.call(val)
|
81
|
-
else
|
82
|
-
custom_inspect.call(val, {multiline: multiline, indent: indent})
|
83
|
-
end
|
84
|
-
elsif rules[:sensitivity] && !rules[:sensitivity].empty? && !val.nil?
|
85
|
-
"<REDACTED #{rules[:sensitivity].join(', ')}>"
|
86
|
-
else
|
87
|
-
val.inspect
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
sig do
|
92
|
-
params(pretty_kvs: T::Array[[Symbol, String]], multiline: T::Boolean, indent: String)
|
93
|
-
.returns(String)
|
94
|
-
end
|
95
|
-
private def join_props_with_pretty_values(pretty_kvs, multiline:, indent: ' ')
|
96
|
-
pairs = pretty_kvs
|
97
|
-
.sort_by {|k, _v| k.to_s}
|
98
|
-
.map {|k, v| "#{k}=#{v}"}
|
99
|
-
|
100
|
-
if multiline
|
101
|
-
indent + pairs.join("\n#{indent}")
|
102
|
-
else
|
103
|
-
pairs.join(', ')
|
104
|
-
end
|
105
|
-
end
|
64
|
+
# Overridable method to add anything that is not a prop
|
65
|
+
# NOTE: This is to support cases like Serializable's `@_extra_props`, and Stripe's `PrettyPrintableModel#@_deleted`
|
66
|
+
sig {params(instance: T::Props::PrettyPrintable, pp: T.any(PrettyPrint, PP::SingleLine)).void}
|
67
|
+
def pretty_print_extra(instance, pp); end
|
106
68
|
end
|
107
69
|
end
|
@@ -176,7 +176,7 @@ module T::Props
|
|
176
176
|
base_message = "Can't set #{klass.name}.#{prop} to #{val.inspect} (instance of #{val.class}) - need a #{type}"
|
177
177
|
|
178
178
|
pretty_message = "Parameter '#{prop}': #{base_message}\n"
|
179
|
-
caller_loc = caller_locations
|
179
|
+
caller_loc = caller_locations.find {|l| !l.to_s.include?('sorbet-runtime/lib/types/props')}
|
180
180
|
if caller_loc
|
181
181
|
pretty_message += "Caller: #{caller_loc.path}:#{caller_loc.lineno}\n"
|
182
182
|
end
|
@@ -223,9 +223,10 @@ module T::Props::Serializable::DecoratorMethods
|
|
223
223
|
end
|
224
224
|
|
225
225
|
def add_prop_definition(prop, rules)
|
226
|
-
|
226
|
+
serialized_form = rules.fetch(:name, prop.to_s)
|
227
|
+
rules[:serialized_form] = serialized_form
|
227
228
|
res = super
|
228
|
-
prop_by_serialized_forms[
|
229
|
+
prop_by_serialized_forms[serialized_form] = prop
|
229
230
|
if T::Configuration.use_vm_prop_serde?
|
230
231
|
enqueue_lazy_vm_method_definition!(:__t_props_generated_serialize) {generate_serialize2}
|
231
232
|
enqueue_lazy_vm_method_definition!(:__t_props_generated_deserialize) {generate_deserialize2}
|
@@ -338,14 +339,21 @@ module T::Props::Serializable::DecoratorMethods
|
|
338
339
|
end
|
339
340
|
end
|
340
341
|
|
341
|
-
#
|
342
|
-
|
342
|
+
# adds to the default result of T::Props::PrettyPrintable
|
343
|
+
def pretty_print_extra(instance, pp)
|
344
|
+
# This is to maintain backwards compatibility with Stripe's codebase, where only the single line (through `inspect`)
|
345
|
+
# version is expected to add anything extra
|
346
|
+
return if !pp.is_a?(PP::SingleLine)
|
343
347
|
if (extra_props = extra_props(instance)) && !extra_props.empty?
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
348
|
+
pp.breakable
|
349
|
+
pp.text("@_extra_props=")
|
350
|
+
pp.group(1, "<", ">") do
|
351
|
+
extra_props.each_with_index do |(prop, value), i|
|
352
|
+
pp.breakable unless i.zero?
|
353
|
+
pp.text("#{prop}=")
|
354
|
+
value.pretty_print(pp)
|
355
|
+
end
|
356
|
+
end
|
349
357
|
end
|
350
358
|
end
|
351
359
|
end
|