sorbet-runtime 0.5.5841

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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sorbet-runtime.rb +116 -0
  3. data/lib/types/_types.rb +285 -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 +95 -0
  7. data/lib/types/configuration.rb +428 -0
  8. data/lib/types/enum.rb +349 -0
  9. data/lib/types/generic.rb +23 -0
  10. data/lib/types/helpers.rb +39 -0
  11. data/lib/types/interface_wrapper.rb +158 -0
  12. data/lib/types/non_forcing_constants.rb +51 -0
  13. data/lib/types/private/abstract/data.rb +36 -0
  14. data/lib/types/private/abstract/declare.rb +48 -0
  15. data/lib/types/private/abstract/hooks.rb +43 -0
  16. data/lib/types/private/abstract/validate.rb +128 -0
  17. data/lib/types/private/casts.rb +22 -0
  18. data/lib/types/private/class_utils.rb +111 -0
  19. data/lib/types/private/decl_state.rb +30 -0
  20. data/lib/types/private/final.rb +51 -0
  21. data/lib/types/private/methods/_methods.rb +460 -0
  22. data/lib/types/private/methods/call_validation.rb +1149 -0
  23. data/lib/types/private/methods/decl_builder.rb +228 -0
  24. data/lib/types/private/methods/modes.rb +16 -0
  25. data/lib/types/private/methods/signature.rb +196 -0
  26. data/lib/types/private/methods/signature_validation.rb +229 -0
  27. data/lib/types/private/mixins/mixins.rb +27 -0
  28. data/lib/types/private/retry.rb +10 -0
  29. data/lib/types/private/runtime_levels.rb +56 -0
  30. data/lib/types/private/sealed.rb +65 -0
  31. data/lib/types/private/types/not_typed.rb +23 -0
  32. data/lib/types/private/types/string_holder.rb +26 -0
  33. data/lib/types/private/types/type_alias.rb +26 -0
  34. data/lib/types/private/types/void.rb +34 -0
  35. data/lib/types/profile.rb +31 -0
  36. data/lib/types/props/_props.rb +161 -0
  37. data/lib/types/props/constructor.rb +40 -0
  38. data/lib/types/props/custom_type.rb +108 -0
  39. data/lib/types/props/decorator.rb +672 -0
  40. data/lib/types/props/errors.rb +8 -0
  41. data/lib/types/props/generated_code_validation.rb +268 -0
  42. data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
  43. data/lib/types/props/optional.rb +81 -0
  44. data/lib/types/props/plugin.rb +37 -0
  45. data/lib/types/props/pretty_printable.rb +107 -0
  46. data/lib/types/props/private/apply_default.rb +170 -0
  47. data/lib/types/props/private/deserializer_generator.rb +165 -0
  48. data/lib/types/props/private/parser.rb +32 -0
  49. data/lib/types/props/private/serde_transform.rb +192 -0
  50. data/lib/types/props/private/serializer_generator.rb +77 -0
  51. data/lib/types/props/private/setter_factory.rb +134 -0
  52. data/lib/types/props/serializable.rb +330 -0
  53. data/lib/types/props/type_validation.rb +111 -0
  54. data/lib/types/props/utils.rb +59 -0
  55. data/lib/types/props/weak_constructor.rb +67 -0
  56. data/lib/types/runtime_profiled.rb +24 -0
  57. data/lib/types/sig.rb +30 -0
  58. data/lib/types/struct.rb +18 -0
  59. data/lib/types/types/attached_class.rb +37 -0
  60. data/lib/types/types/base.rb +151 -0
  61. data/lib/types/types/class_of.rb +38 -0
  62. data/lib/types/types/enum.rb +42 -0
  63. data/lib/types/types/fixed_array.rb +60 -0
  64. data/lib/types/types/fixed_hash.rb +59 -0
  65. data/lib/types/types/intersection.rb +37 -0
  66. data/lib/types/types/noreturn.rb +29 -0
  67. data/lib/types/types/proc.rb +51 -0
  68. data/lib/types/types/self_type.rb +35 -0
  69. data/lib/types/types/simple.rb +33 -0
  70. data/lib/types/types/t_enum.rb +38 -0
  71. data/lib/types/types/type_member.rb +7 -0
  72. data/lib/types/types/type_parameter.rb +23 -0
  73. data/lib/types/types/type_template.rb +7 -0
  74. data/lib/types/types/type_variable.rb +31 -0
  75. data/lib/types/types/typed_array.rb +34 -0
  76. data/lib/types/types/typed_enumerable.rb +161 -0
  77. data/lib/types/types/typed_enumerator.rb +36 -0
  78. data/lib/types/types/typed_hash.rb +43 -0
  79. data/lib/types/types/typed_range.rb +26 -0
  80. data/lib/types/types/typed_set.rb +36 -0
  81. data/lib/types/types/union.rb +56 -0
  82. data/lib/types/types/untyped.rb +29 -0
  83. data/lib/types/utils.rb +217 -0
  84. metadata +223 -0
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::Plugin
5
+ include T::Props
6
+ extend T::Helpers
7
+
8
+ module ClassMethods
9
+ def included(child)
10
+ super
11
+ child.plugin(self)
12
+ end
13
+ end
14
+ mixes_in_class_methods(ClassMethods)
15
+
16
+ module Private
17
+ # These need to be non-instance methods so we can use them without prematurely creating the
18
+ # child decorator in `model_inherited` (see comments there for details).
19
+ #
20
+ # The dynamic constant access below forces this file to be `typed: false`
21
+ def self.apply_class_methods(plugin, target)
22
+ if plugin.const_defined?('ClassMethods')
23
+ # FIXME: This will break preloading, selective test execution, etc if `mod::ClassMethods`
24
+ # is ever defined in a separate file from `mod`.
25
+ target.extend(plugin::ClassMethods) # rubocop:disable PrisonGuard/NoDynamicConstAccess
26
+ end
27
+ end
28
+
29
+ def self.apply_decorator_methods(plugin, target)
30
+ if plugin.const_defined?('DecoratorMethods')
31
+ # FIXME: This will break preloading, selective test execution, etc if `mod::DecoratorMethods`
32
+ # is ever defined in a separate file from `mod`.
33
+ target.extend(plugin::DecoratorMethods) # rubocop:disable PrisonGuard/NoDynamicConstAccess
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Props::PrettyPrintable
5
+ include T::Props::Plugin
6
+
7
+ # Return a string representation of this object and all of its props
8
+ def inspect
9
+ T.unsafe(T.cast(self, Object).class).decorator.inspect_instance(self)
10
+ end
11
+
12
+ # Override the PP gem with something that's similar, but gives us a hook
13
+ # to do redaction
14
+ def pretty_inspect
15
+ T.unsafe(T.cast(self, Object).class).decorator.inspect_instance(self, multiline: true)
16
+ end
17
+
18
+ module DecoratorMethods
19
+ extend T::Sig
20
+
21
+ sig {params(key: Symbol).returns(T::Boolean).checked(:never)}
22
+ def valid_rule_key?(key)
23
+ super || key == :inspect
24
+ end
25
+
26
+ sig do
27
+ params(instance: T::Props::PrettyPrintable, multiline: T::Boolean, indent: String)
28
+ .returns(String)
29
+ 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
+
64
+ [
65
+ T.unsafe(self).decorated_class.to_s,
66
+ joined_props,
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
106
+ end
107
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module T::Props
5
+ module Private
6
+ class ApplyDefault
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ abstract!
10
+
11
+ # checked(:never) - O(object construction x prop count)
12
+ sig {returns(SetterFactory::SetterProc).checked(:never)}
13
+ attr_reader :setter_proc
14
+
15
+ # checked(:never) - We do this with `T.let` instead
16
+ sig {params(accessor_key: Symbol, setter_proc: SetterFactory::SetterProc).void.checked(:never)}
17
+ def initialize(accessor_key, setter_proc)
18
+ @accessor_key = T.let(accessor_key, Symbol)
19
+ @setter_proc = T.let(setter_proc, SetterFactory::SetterProc)
20
+ end
21
+
22
+ # checked(:never) - O(object construction x prop count)
23
+ sig {abstract.returns(T.untyped).checked(:never)}
24
+ def default; end
25
+
26
+ # checked(:never) - O(object construction x prop count)
27
+ sig {abstract.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
28
+ def set_default(instance); end
29
+
30
+ NO_CLONE_TYPES = T.let([TrueClass, FalseClass, NilClass, Symbol, Numeric, T::Enum].freeze, T::Array[Module])
31
+
32
+ # checked(:never) - Rules hash is expensive to check
33
+ sig {params(cls: Module, rules: T::Hash[Symbol, T.untyped]).returns(T.nilable(ApplyDefault)).checked(:never)}
34
+ def self.for(cls, rules)
35
+ accessor_key = rules.fetch(:accessor_key)
36
+ setter = rules.fetch(:setter_proc)
37
+
38
+ if rules.key?(:factory)
39
+ ApplyDefaultFactory.new(cls, rules.fetch(:factory), accessor_key, setter)
40
+ elsif rules.key?(:default)
41
+ default = rules.fetch(:default)
42
+ case default
43
+ when *NO_CLONE_TYPES
44
+ return ApplyPrimitiveDefault.new(default, accessor_key, setter)
45
+ when String
46
+ if default.frozen?
47
+ return ApplyPrimitiveDefault.new(default, accessor_key, setter)
48
+ end
49
+ when Array
50
+ if default.empty? && default.class == Array
51
+ return ApplyEmptyArrayDefault.new(accessor_key, setter)
52
+ end
53
+ when Hash
54
+ if default.empty? && default.default.nil? && T.unsafe(default).default_proc.nil? && default.class == Hash
55
+ return ApplyEmptyHashDefault.new(accessor_key, setter)
56
+ end
57
+ end
58
+
59
+ ApplyComplexDefault.new(default, accessor_key, setter)
60
+ else
61
+ nil
62
+ end
63
+ end
64
+ end
65
+
66
+ class ApplyFixedDefault < ApplyDefault
67
+ abstract!
68
+
69
+ # checked(:never) - We do this with `T.let` instead
70
+ sig {params(default: BasicObject, accessor_key: Symbol, setter_proc: SetterFactory::SetterProc).void.checked(:never)}
71
+ def initialize(default, accessor_key, setter_proc)
72
+ # FIXME: Ideally we'd check here that the default is actually a valid
73
+ # value for this field, but existing code relies on the fact that we don't.
74
+ #
75
+ # :(
76
+ #
77
+ # setter_proc.call(default)
78
+ @default = T.let(default, BasicObject)
79
+ super(accessor_key, setter_proc)
80
+ end
81
+
82
+ # checked(:never) - O(object construction x prop count)
83
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
84
+ def set_default(instance)
85
+ instance.instance_variable_set(@accessor_key, default)
86
+ end
87
+ end
88
+
89
+ class ApplyPrimitiveDefault < ApplyFixedDefault
90
+ # checked(:never) - O(object construction x prop count)
91
+ sig {override.returns(T.untyped).checked(:never)}
92
+ attr_reader :default
93
+ end
94
+
95
+ class ApplyComplexDefault < ApplyFixedDefault
96
+ # checked(:never) - O(object construction x prop count)
97
+ sig {override.returns(T.untyped).checked(:never)}
98
+ def default
99
+ T::Props::Utils.deep_clone_object(@default)
100
+ end
101
+ end
102
+
103
+ # Special case since it's so common, and a literal `[]` is meaningfully
104
+ # faster than falling back to ApplyComplexDefault or even calling
105
+ # `some_empty_array.dup`
106
+ class ApplyEmptyArrayDefault < ApplyDefault
107
+ # checked(:never) - O(object construction x prop count)
108
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
109
+ def set_default(instance)
110
+ instance.instance_variable_set(@accessor_key, [])
111
+ end
112
+
113
+ # checked(:never) - O(object construction x prop count)
114
+ sig {override.returns(T::Array[T.untyped]).checked(:never)}
115
+ def default
116
+ []
117
+ end
118
+ end
119
+
120
+ # Special case since it's so common, and a literal `{}` is meaningfully
121
+ # faster than falling back to ApplyComplexDefault or even calling
122
+ # `some_empty_hash.dup`
123
+ class ApplyEmptyHashDefault < ApplyDefault
124
+ # checked(:never) - O(object construction x prop count)
125
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
126
+ def set_default(instance)
127
+ instance.instance_variable_set(@accessor_key, {})
128
+ end
129
+
130
+ # checked(:never) - O(object construction x prop count)
131
+ sig {override.returns(T::Hash[T.untyped, T.untyped]).checked(:never)}
132
+ def default
133
+ {}
134
+ end
135
+ end
136
+
137
+ class ApplyDefaultFactory < ApplyDefault
138
+ # checked(:never) - We do this with `T.let` instead
139
+ sig do
140
+ params(
141
+ cls: Module,
142
+ factory: T.any(Proc, Method),
143
+ accessor_key: Symbol,
144
+ setter_proc: SetterFactory::SetterProc,
145
+ )
146
+ .void
147
+ .checked(:never)
148
+ end
149
+ def initialize(cls, factory, accessor_key, setter_proc)
150
+ @class = T.let(cls, Module)
151
+ @factory = T.let(factory, T.any(Proc, Method))
152
+ super(accessor_key, setter_proc)
153
+ end
154
+
155
+ # checked(:never) - O(object construction x prop count)
156
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
157
+ def set_default(instance)
158
+ # Use the actual setter to validate the factory returns a legitimate
159
+ # value every time
160
+ instance.instance_exec(default, &@setter_proc)
161
+ end
162
+
163
+ # checked(:never) - O(object construction x prop count)
164
+ sig {override.returns(T.untyped).checked(:never)}
165
+ def default
166
+ @class.class_exec(&@factory)
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module T::Props
5
+ module Private
6
+
7
+ # Generates a specialized `deserialize` implementation for a subclass of
8
+ # T::Props::Serializable.
9
+ #
10
+ # The basic idea is that we analyze the props and for each prop, generate
11
+ # the simplest possible logic as a block of Ruby source, so that we don't
12
+ # pay the cost of supporting types like T:::Hash[CustomType, SubstructType]
13
+ # when deserializing a simple Integer. Then we join those together,
14
+ # with a little shared logic to be able to detect when we get input keys
15
+ # that don't match any prop.
16
+ module DeserializerGenerator
17
+ extend T::Sig
18
+
19
+ # Generate a method that takes a T::Hash[String, T.untyped] representing
20
+ # serialized props, sets instance variables for each prop found in the
21
+ # input, and returns the count of we props set (which we can use to check
22
+ # for unexpected input keys with minimal effect on the fast path).
23
+ sig do
24
+ params(
25
+ props: T::Hash[Symbol, T::Hash[Symbol, T.untyped]],
26
+ defaults: T::Hash[Symbol, T::Props::Private::ApplyDefault],
27
+ )
28
+ .returns(String)
29
+ .checked(:never)
30
+ end
31
+ def self.generate(props, defaults)
32
+ stored_props = props.reject {|_, rules| rules[:dont_store]}
33
+ parts = stored_props.map do |prop, rules|
34
+ # All of these strings should already be validated (directly or
35
+ # indirectly) in `validate_prop_name`, so we don't bother with a nice
36
+ # error message, but we double check here to prevent a refactoring
37
+ # from introducing a security vulnerability.
38
+ raise unless T::Props::Decorator::SAFE_NAME.match?(prop.to_s)
39
+
40
+ hash_key = rules.fetch(:serialized_form)
41
+ raise unless T::Props::Decorator::SAFE_NAME.match?(hash_key)
42
+
43
+ ivar_name = rules.fetch(:accessor_key).to_s
44
+ raise unless ivar_name.start_with?('@') && T::Props::Decorator::SAFE_NAME.match?(ivar_name[1..-1])
45
+
46
+ transformation = SerdeTransform.generate(
47
+ T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object)),
48
+ SerdeTransform::Mode::DESERIALIZE,
49
+ 'val'
50
+ )
51
+ if transformation
52
+ # Rescuing exactly NoMethodError is intended as a temporary hack
53
+ # to preserve the semantics from before codegen. More generally
54
+ # we are inconsistent about typechecking on deser and need to decide
55
+ # our strategy here.
56
+ transformed_val = <<~RUBY
57
+ begin
58
+ #{transformation}
59
+ rescue NoMethodError => e
60
+ T::Configuration.soft_assert_handler(
61
+ 'Deserialization error (probably unexpected stored type)',
62
+ storytime: {
63
+ klass: self.class,
64
+ prop: #{prop.inspect},
65
+ value: val,
66
+ error: e.message,
67
+ notify: 'djudd'
68
+ }
69
+ )
70
+ val
71
+ end
72
+ RUBY
73
+ else
74
+ transformed_val = 'val'
75
+ end
76
+
77
+ nil_handler = generate_nil_handler(
78
+ prop: prop,
79
+ serialized_form: hash_key,
80
+ default: defaults[prop],
81
+ nilable_type: T::Props::Utils.optional_prop?(rules),
82
+ raise_on_nil_write: !!rules[:raise_on_nil_write],
83
+ )
84
+
85
+ <<~RUBY
86
+ val = hash[#{hash_key.inspect}]
87
+ #{ivar_name} = if val.nil?
88
+ found -= 1 unless hash.key?(#{hash_key.inspect})
89
+ #{nil_handler}
90
+ else
91
+ #{transformed_val}
92
+ end
93
+ RUBY
94
+ end
95
+
96
+ <<~RUBY
97
+ def __t_props_generated_deserialize(hash)
98
+ found = #{stored_props.size}
99
+ #{parts.join("\n\n")}
100
+ found
101
+ end
102
+ RUBY
103
+ end
104
+
105
+ # This is very similar to what we do in ApplyDefault, but has a few
106
+ # key differences that mean we don't just re-use the code:
107
+ #
108
+ # 1. Where the logic in construction is that we generate a default
109
+ # if & only if the prop key isn't present in the input, here we'll
110
+ # generate a default even to override an explicit nil, but only
111
+ # if the prop is actually required.
112
+ # 2. Since we're generating raw Ruby source, we can remove a layer
113
+ # of indirection for marginally better performance; this seems worth
114
+ # it for the common cases of literals and empty arrays/hashes.
115
+ # 3. We need to care about the distinction between `raise_on_nil_write`
116
+ # and actually non-nilable, where new-instance construction doesn't.
117
+ #
118
+ # So we fall back to ApplyDefault only when one of the cases just
119
+ # mentioned doesn't apply.
120
+ sig do
121
+ params(
122
+ prop: Symbol,
123
+ serialized_form: String,
124
+ default: T.nilable(ApplyDefault),
125
+ nilable_type: T::Boolean,
126
+ raise_on_nil_write: T::Boolean,
127
+ )
128
+ .returns(String)
129
+ .checked(:never)
130
+ end
131
+ private_class_method def self.generate_nil_handler(
132
+ prop:,
133
+ serialized_form:,
134
+ default:,
135
+ nilable_type:,
136
+ raise_on_nil_write:
137
+ )
138
+ if !nilable_type
139
+ case default
140
+ when NilClass
141
+ "self.class.decorator.raise_nil_deserialize_error(#{serialized_form.inspect})"
142
+ when ApplyPrimitiveDefault
143
+ literal = default.default
144
+ case literal
145
+ when String, Integer, Symbol, Float, TrueClass, FalseClass, NilClass
146
+ literal.inspect
147
+ else
148
+ "self.class.decorator.props_with_defaults.fetch(#{prop.inspect}).default"
149
+ end
150
+ when ApplyEmptyArrayDefault
151
+ '[]'
152
+ when ApplyEmptyHashDefault
153
+ '{}'
154
+ else
155
+ "self.class.decorator.props_with_defaults.fetch(#{prop.inspect}).default"
156
+ end
157
+ elsif raise_on_nil_write
158
+ "required_prop_missing_from_deserialize(#{prop.inspect})"
159
+ else
160
+ 'nil'
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end