sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +100 -0
  3. data/lib/types/_types.rb +245 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +37 -0
  7. data/lib/types/configuration.rb +368 -0
  8. data/lib/types/generic.rb +23 -0
  9. data/lib/types/helpers.rb +31 -0
  10. data/lib/types/interface_wrapper.rb +158 -0
  11. data/lib/types/private/abstract/data.rb +36 -0
  12. data/lib/types/private/abstract/declare.rb +39 -0
  13. data/lib/types/private/abstract/hooks.rb +43 -0
  14. data/lib/types/private/abstract/validate.rb +128 -0
  15. data/lib/types/private/casts.rb +22 -0
  16. data/lib/types/private/class_utils.rb +102 -0
  17. data/lib/types/private/decl_state.rb +18 -0
  18. data/lib/types/private/error_handler.rb +37 -0
  19. data/lib/types/private/methods/_methods.rb +344 -0
  20. data/lib/types/private/methods/call_validation.rb +1177 -0
  21. data/lib/types/private/methods/decl_builder.rb +275 -0
  22. data/lib/types/private/methods/modes.rb +18 -0
  23. data/lib/types/private/methods/signature.rb +196 -0
  24. data/lib/types/private/methods/signature_validation.rb +232 -0
  25. data/lib/types/private/mixins/mixins.rb +27 -0
  26. data/lib/types/private/runtime_levels.rb +41 -0
  27. data/lib/types/private/types/not_typed.rb +23 -0
  28. data/lib/types/private/types/string_holder.rb +26 -0
  29. data/lib/types/private/types/void.rb +33 -0
  30. data/lib/types/profile.rb +27 -0
  31. data/lib/types/props/_props.rb +165 -0
  32. data/lib/types/props/constructor.rb +20 -0
  33. data/lib/types/props/custom_type.rb +84 -0
  34. data/lib/types/props/decorator.rb +826 -0
  35. data/lib/types/props/errors.rb +8 -0
  36. data/lib/types/props/optional.rb +73 -0
  37. data/lib/types/props/plugin.rb +15 -0
  38. data/lib/types/props/pretty_printable.rb +106 -0
  39. data/lib/types/props/serializable.rb +376 -0
  40. data/lib/types/props/type_validation.rb +98 -0
  41. data/lib/types/props/utils.rb +49 -0
  42. data/lib/types/props/weak_constructor.rb +30 -0
  43. data/lib/types/runtime_profiled.rb +36 -0
  44. data/lib/types/sig.rb +28 -0
  45. data/lib/types/struct.rb +8 -0
  46. data/lib/types/types/base.rb +141 -0
  47. data/lib/types/types/class_of.rb +38 -0
  48. data/lib/types/types/enum.rb +42 -0
  49. data/lib/types/types/fixed_array.rb +60 -0
  50. data/lib/types/types/fixed_hash.rb +59 -0
  51. data/lib/types/types/intersection.rb +36 -0
  52. data/lib/types/types/noreturn.rb +25 -0
  53. data/lib/types/types/proc.rb +51 -0
  54. data/lib/types/types/self_type.rb +31 -0
  55. data/lib/types/types/simple.rb +33 -0
  56. data/lib/types/types/type_member.rb +7 -0
  57. data/lib/types/types/type_parameter.rb +23 -0
  58. data/lib/types/types/type_template.rb +7 -0
  59. data/lib/types/types/type_variable.rb +31 -0
  60. data/lib/types/types/typed_array.rb +20 -0
  61. data/lib/types/types/typed_enumerable.rb +141 -0
  62. data/lib/types/types/typed_enumerator.rb +22 -0
  63. data/lib/types/types/typed_hash.rb +29 -0
  64. data/lib/types/types/typed_range.rb +22 -0
  65. data/lib/types/types/typed_set.rb +22 -0
  66. data/lib/types/types/union.rb +59 -0
  67. data/lib/types/types/untyped.rb +25 -0
  68. data/lib/types/utils.rb +223 -0
  69. metadata +122 -15
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::TypeValidation
5
+ include T::Props::Plugin
6
+
7
+ BANNED_TYPES = [Object, BasicObject, Kernel]
8
+
9
+ class UnderspecifiedType < ArgumentError; end
10
+
11
+ module DecoratorMethods
12
+ extend T::Sig
13
+
14
+ sig {returns(T::Array[Symbol])}
15
+ def valid_props
16
+ super + [:DEPRECATED_underspecified_type]
17
+ end
18
+
19
+ sig do
20
+ params(
21
+ name: T.any(Symbol, String),
22
+ _cls: Module,
23
+ rules: T::Hash[Symbol, T.untyped],
24
+ type: T.any(T::Types::Base, Module)
25
+ )
26
+ .void
27
+ end
28
+ def prop_validate_definition!(name, _cls, rules, type)
29
+ super
30
+
31
+ if !rules[:DEPRECATED_underspecified_type] && !(type.singleton_class <= T::Props::CustomType)
32
+ validate_type(type, field_name: name)
33
+ elsif rules[:DEPRECATED_underspecified_type] && find_invalid_subtype(type).nil?
34
+ raise ArgumentError.new("DEPRECATED_underspecified_type set unnecessarily for #{@class.name}.#{name} - #{type} is a valid type")
35
+ end
36
+ end
37
+
38
+ sig do
39
+ params(
40
+ type: T::Types::Base,
41
+ field_name: T.any(Symbol, String),
42
+ )
43
+ .void
44
+ end
45
+ private def validate_type(type, field_name:)
46
+ if (invalid_subtype = find_invalid_subtype(type))
47
+ raise UnderspecifiedType.new(type_error_message(invalid_subtype, field_name, type))
48
+ end
49
+ end
50
+
51
+ # Returns an invalid type, if any, found in the given top-level type.
52
+ # This might be the type itself, if it is e.g. "Object", or might be
53
+ # a subtype like the type of the values of a typed hash.
54
+ #
55
+ # If the type is fully valid, returns nil.
56
+ sig {params(type: T::Types::Base).returns(T.nilable(T::Types::Base))}
57
+ private def find_invalid_subtype(type)
58
+ case type
59
+ when T::Types::TypedEnumerable
60
+ find_invalid_subtype(type.type)
61
+ when T::Types::FixedHash
62
+ type.types.values.map {|subtype| find_invalid_subtype(subtype)}.compact.first
63
+ when T::Types::Union, T::Types::FixedArray
64
+ type.types.map {|subtype| find_invalid_subtype(subtype)}.compact.first
65
+ when T::Types::Enum, T::Types::ClassOf
66
+ nil
67
+ when T::Types::Simple
68
+ # TODO Could we manage to define a whitelist, consisting of something
69
+ # like primitives, subdocs, DataInterfaces, and collections/enums/unions
70
+ # thereof?
71
+ if BANNED_TYPES.include?(type.raw_type)
72
+ type
73
+ else
74
+ nil
75
+ end
76
+ else
77
+ type
78
+ end
79
+ end
80
+
81
+ sig do
82
+ params(
83
+ type: T::Types::Base,
84
+ field_name: T.any(Symbol, String),
85
+ orig_type: T::Types::Base,
86
+ )
87
+ .returns(String)
88
+ end
89
+ private def type_error_message(type, field_name, orig_type)
90
+ msg_prefix = "#{@class.name}.#{field_name}: #{orig_type} is invalid in prop definition"
91
+ if type == orig_type
92
+ "#{msg_prefix}. Please choose a more specific type (T.untyped and ~equivalents like Object are banned)."
93
+ else
94
+ "#{msg_prefix}. Please choose a subtype more specific than #{type} (T.untyped and ~equivalents like Object are banned)."
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Props::Utils
5
+ # Deep copy an object. The object must consist of Ruby primitive
6
+ # types and Hashes and Arrays.
7
+ def self.deep_clone_object(what, freeze: false)
8
+ result = case what
9
+ when true
10
+ true
11
+ when false
12
+ false
13
+ when Symbol, NilClass, Numeric
14
+ what
15
+ when Array
16
+ what.map {|v| deep_clone_object(v, freeze: freeze)}
17
+ when Hash
18
+ h = what.class.new
19
+ what.each do |k, v|
20
+ k.freeze if freeze
21
+ h[k] = deep_clone_object(v, freeze: freeze)
22
+ end
23
+ h
24
+ when Regexp
25
+ what.dup
26
+ else
27
+ # Some unfortunate nastiness to get around Opus::Enum potentially not
28
+ # being defined.
29
+ if defined?(Opus) && defined?(Opus::Enum) && what.class == Opus::Enum
30
+ what
31
+ else
32
+ what.clone
33
+ end
34
+ end
35
+ freeze ? result.freeze : result
36
+ end
37
+
38
+ # The prop_rules indicate whether we should check for reading a nil value for the prop/field.
39
+ # This is mostly for the compatibility check that we allow existing documents carry some nil prop/field.
40
+ def self.need_nil_read_check?(prop_rules)
41
+ # . :on_load allows nil read, but we need to check for the read for future writes
42
+ prop_rules[:optional] == :on_load || prop_rules[:raise_on_nil_write]
43
+ end
44
+
45
+ # The prop_rules indicate whether we should check for writing a nil value for the prop/field.
46
+ def self.need_nil_write_check?(prop_rules)
47
+ need_nil_read_check?(prop_rules) || T::Utils::Props.required_prop?(prop_rules)
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::WeakConstructor
5
+ include T::Props::Optional
6
+
7
+ def initialize(hash={})
8
+ expected_keys = {}
9
+ hash.each_key {|key| expected_keys[key] = true}
10
+
11
+ decorator = self.class.decorator
12
+
13
+ decorator.props.each do |p, rules|
14
+ if hash.key?(p)
15
+ expected_keys.delete(p)
16
+ val = hash[p]
17
+ elsif decorator.has_default?(rules)
18
+ val = decorator.get_default(rules, self.class)
19
+ else
20
+ next
21
+ end
22
+
23
+ decorator.prop_set(self, p, val, rules)
24
+ end
25
+
26
+ unless expected_keys.empty?
27
+ raise ArgumentError.new("#{@class}: Unrecognized properties in #with_props: #{expected_keys.keys.join(', ')}")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # Hey there!
6
+ #
7
+ # T::Utils::Runtime is just a random class. Chances are[^1] it's not a class
8
+ # that your variables are instances of, which means that using it in a type
9
+ # signature will always create a type error.
10
+ #
11
+ # At first, it doesn't seem like a good thing to have a type that's sole
12
+ # purpose is to cause type errors. The trick is that within the ruby-types
13
+ # team, we only use T::Utils::RuntimeProfiled within 'generated' sigs.
14
+ #
15
+ # Unlike normal sigs, generated sigs never raise at runtime. They also log the
16
+ # actual, observed type on type error to a central location. We're using these
17
+ # observed types to refine and expand our type coverage in pay-server.
18
+ #
19
+ # What does this all mean for you?
20
+ #
21
+ # - If you were just curious, that's it! Leave the sig as is, and carry on.
22
+ # - If you wanted to replace this sig with a better, hand-authored one:
23
+ #
24
+ # 1. Remove 'generated' from the sig.
25
+ # 2. Update the sig to your liking
26
+ #
27
+ # Questions? :portal-to: => #ruby-types
28
+ #
29
+ # [^1]: Unless you happen to be calling T::Utils::RuntimeProfiled.new directly...
30
+ #
31
+
32
+ module T; end
33
+ module T::Utils; end
34
+ # Sorbet guesses this type instead of T.untyped when passed --suggest-runtime-profiled
35
+
36
+ class T::Utils::RuntimeProfiled; end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+ # rubocop:disable PrisonGuard/NoTopLevelDeclarations, PrisonGuard/PackageMatchesDirectory
4
+
5
+ # Used as a mixin to any class so that you can call `sig`.
6
+ # Docs at http://go/types
7
+ module T::Sig
8
+ module WithoutRuntime
9
+ # At runtime, does nothing, but statically it is treated exactly the same
10
+ # as T::Sig#sig. Only use it in cases where you can't use T::Sig#sig.
11
+ def self.sig(&blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
12
+
13
+ # At runtime, does nothing, but statically it is treated exactly the same
14
+ # as T::Sig#sig. Only use it in cases where you can't use T::Sig#sig.
15
+ T::Sig::WithoutRuntime.sig {params(blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void} # rubocop:disable PrisonGuard/PrivateModule
16
+ def self.sig(&blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride, Lint/DuplicateMethods
17
+ end
18
+
19
+ # Declares a method with type signatures and/or
20
+ # abstract/override/... helpers. See the documentation URL on
21
+ # {T::Helpers}
22
+ T::Sig::WithoutRuntime.sig {params(blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
23
+ def sig(&blk) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
24
+ T::Private::Methods.declare_sig(self, &blk)
25
+ end
26
+ end
27
+
28
+ # rubocop:enable PrisonGuard/NoTopLevelDeclarations, PrisonGuard/PackageMatchesDirectory
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ class T::Struct
5
+ include T::Props
6
+ include T::Props::Serializable
7
+ include T::Props::Constructor
8
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Types
5
+ class Base
6
+ def self.method_added(method_name)
7
+ # What is now `subtype_of_single?` used to be named `subtype_of?`. Make sure people don't
8
+ # override the wrong thing.
9
+ #
10
+ # NB: Outside of T::Types, we would enforce this by using `sig` and not declaring the method
11
+ # as overridable, but doing so here would result in a dependency cycle.
12
+ if method_name == :subtype_of? && self != T::Types::Base
13
+ raise "`subtype_of?` should not be overridden. You probably want to override " \
14
+ "`subtype_of_single?` instead."
15
+ end
16
+ end
17
+
18
+ def valid?(obj)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ # @return [T::Boolean] This method must be implemented to return whether the subclass is a subtype
23
+ # of `type`. This should only be called by `subtype_of?`, which guarantees that `type` will be
24
+ # a "single" type, by which we mean it won't be a Union or an Intersection (c.f.
25
+ # `isSubTypeSingle` in sorbet).
26
+ private def subtype_of_single?(type)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ # Equality is based on name, so be sure the name reflects all relevant state when implementing.
31
+ def name
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # Mirrors ruby_typer::core::Types::isSubType
36
+ # See https://git.corp.stripe.com/stripe-internal/ruby-typer/blob/9fc8ed998c04ac0b96592ae6bb3493b8a925c5c1/core/types/subtyping.cc#L912-L950
37
+ #
38
+ # This method cannot be overridden (see `method_added` above).
39
+ # Subclasses only need to implement `subtype_of_single?`).
40
+ def subtype_of?(t2)
41
+ t1 = self
42
+
43
+ # pairs to cover: 1 (_, _)
44
+ # 2 (_, And)
45
+ # 3 (_, Or)
46
+ # 4 (And, _)
47
+ # 5 (And, And)
48
+ # 6 (And, Or)
49
+ # 7 (Or, _)
50
+ # 8 (Or, And)
51
+ # 9 (Or, Or)
52
+
53
+ # Note: order of cases here matters!
54
+ if t1.is_a?(T::Types::Union) # 7, 8, 9
55
+ # this will be incorrect if/when we have Type members
56
+ return t1.types.all? {|t1_member| t1_member.subtype_of?(t2)}
57
+ end
58
+
59
+ if t2.is_a?(T::Types::Intersection) # 2, 5
60
+ # this will be incorrect if/when we have Type members
61
+ return t2.types.all? {|t2_member| t1.subtype_of?(t2_member)}
62
+ end
63
+
64
+ if t2.is_a?(T::Types::Union)
65
+ if t1.is_a?(T::Types::Intersection) # 6
66
+ # dropping either of parts eagerly make subtype test be too strict.
67
+ # we have to try both cases, when we normally try only one
68
+ return t2.types.any? {|t2_member| t1.subtype_of?(t2_member)} ||
69
+ t1.types.any? {|t1_member| t1_member.subtype_of?(t2)}
70
+ end
71
+ return t2.types.any? {|t2_member| t1.subtype_of?(t2_member)} # 3
72
+ end
73
+
74
+ if t1.is_a?(T::Types::Intersection) # 4
75
+ # this will be incorrect if/when we have Type members
76
+ return t1.types.any? {|t1_member| t1_member.subtype_of?(t2)}
77
+ end
78
+
79
+ # 1; Start with some special cases
80
+ if t1.is_a?(T::Private::Types::Void)
81
+ return t2.is_a?(T::Private::Types::Void)
82
+ end
83
+
84
+ if t1.is_a?(T::Types::Untyped) || t2.is_a?(T::Types::Untyped)
85
+ return true
86
+ end
87
+
88
+ # Rest of (1)
89
+ subtype_of_single?(t2)
90
+ end
91
+
92
+ def to_s
93
+ name
94
+ end
95
+
96
+ def describe_obj(obj)
97
+ # Would be redundant to print class and value in these common cases.
98
+ case obj
99
+ when nil, true, false
100
+ return "type #{obj.class}"
101
+ end
102
+
103
+ # In rare cases, obj.inspect may fail, or be undefined, so rescue.
104
+ begin
105
+ # Default inspect behavior of, eg; `#<Object:0x0...>` is ugly; just print the hash instead, which is more concise/readable.
106
+ if obj.method(:inspect).owner == Kernel
107
+ "type #{obj.class} with hash #{obj.hash}"
108
+ else
109
+ "type #{obj.class} with value #{T::Utils.string_truncate_middle(obj.inspect, 30, 30)}"
110
+ end
111
+ rescue StandardError, SystemStackError
112
+ "type #{obj.class} with unprintable value"
113
+ end
114
+ end
115
+
116
+ def error_message_for_obj(obj)
117
+ if valid?(obj)
118
+ nil
119
+ else
120
+ "Expected type #{self.name}, got #{describe_obj(obj)}"
121
+ end
122
+ end
123
+
124
+ def validate!(obj)
125
+ err = error_message_for_obj(obj)
126
+ raise TypeError.new(err) if err
127
+ end
128
+
129
+ ### Equality methods (necessary for deduping types with `uniq`)
130
+
131
+ def hash
132
+ name.hash
133
+ end
134
+
135
+ def ==(other)
136
+ other.class == self.class && other.name == self.name
137
+ end
138
+
139
+ alias_method :eql?, :==
140
+ end
141
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Types
5
+ # Validates that an object belongs to the specified class.
6
+ class ClassOf < Base
7
+ attr_reader :type
8
+
9
+ def initialize(type)
10
+ @type = type
11
+ end
12
+
13
+ # @override Base
14
+ def name
15
+ "T.class_of(#{@type})"
16
+ end
17
+
18
+ # @override Base
19
+ def valid?(obj)
20
+ obj.is_a?(Module) && obj <= @type
21
+ end
22
+
23
+ # @override Base
24
+ def subtype_of_single?(other)
25
+ case other
26
+ when ClassOf
27
+ @type <= other.type
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ # @override Base
34
+ def describe_obj(obj)
35
+ obj.inspect
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Types
5
+ # validates that the provided value is within a given set/enum
6
+ class Enum < Base
7
+ extend T::Sig
8
+
9
+ attr_reader :values
10
+
11
+ # TODO Ideally Hash would not be accepted but there are a lot of uses with prop enum.
12
+ sig {params(values: T.any(Array, Set, Hash, T::Range[T.untyped])).void}
13
+ def initialize(values)
14
+ @values = values
15
+ end
16
+
17
+ # @override Base
18
+ def valid?(obj)
19
+ @values.member?(obj)
20
+ end
21
+
22
+ # @override Base
23
+ private def subtype_of_single?(other)
24
+ case other
25
+ when Enum
26
+ (other.values - @values).empty?
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ # @override Base
33
+ def name
34
+ "T.enum([#{@values.map(&:inspect).join(', ')}])"
35
+ end
36
+
37
+ # @override Base
38
+ def describe_obj(obj)
39
+ obj.inspect
40
+ end
41
+ end
42
+ end