sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +100 -0
  3. data/lib/types/_types.rb +245 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +37 -0
  7. data/lib/types/configuration.rb +368 -0
  8. data/lib/types/generic.rb +23 -0
  9. data/lib/types/helpers.rb +31 -0
  10. data/lib/types/interface_wrapper.rb +158 -0
  11. data/lib/types/private/abstract/data.rb +36 -0
  12. data/lib/types/private/abstract/declare.rb +39 -0
  13. data/lib/types/private/abstract/hooks.rb +43 -0
  14. data/lib/types/private/abstract/validate.rb +128 -0
  15. data/lib/types/private/casts.rb +22 -0
  16. data/lib/types/private/class_utils.rb +102 -0
  17. data/lib/types/private/decl_state.rb +18 -0
  18. data/lib/types/private/error_handler.rb +37 -0
  19. data/lib/types/private/methods/_methods.rb +344 -0
  20. data/lib/types/private/methods/call_validation.rb +1177 -0
  21. data/lib/types/private/methods/decl_builder.rb +275 -0
  22. data/lib/types/private/methods/modes.rb +18 -0
  23. data/lib/types/private/methods/signature.rb +196 -0
  24. data/lib/types/private/methods/signature_validation.rb +232 -0
  25. data/lib/types/private/mixins/mixins.rb +27 -0
  26. data/lib/types/private/runtime_levels.rb +41 -0
  27. data/lib/types/private/types/not_typed.rb +23 -0
  28. data/lib/types/private/types/string_holder.rb +26 -0
  29. data/lib/types/private/types/void.rb +33 -0
  30. data/lib/types/profile.rb +27 -0
  31. data/lib/types/props/_props.rb +165 -0
  32. data/lib/types/props/constructor.rb +20 -0
  33. data/lib/types/props/custom_type.rb +84 -0
  34. data/lib/types/props/decorator.rb +826 -0
  35. data/lib/types/props/errors.rb +8 -0
  36. data/lib/types/props/optional.rb +73 -0
  37. data/lib/types/props/plugin.rb +15 -0
  38. data/lib/types/props/pretty_printable.rb +106 -0
  39. data/lib/types/props/serializable.rb +376 -0
  40. data/lib/types/props/type_validation.rb +98 -0
  41. data/lib/types/props/utils.rb +49 -0
  42. data/lib/types/props/weak_constructor.rb +30 -0
  43. data/lib/types/runtime_profiled.rb +36 -0
  44. data/lib/types/sig.rb +28 -0
  45. data/lib/types/struct.rb +8 -0
  46. data/lib/types/types/base.rb +141 -0
  47. data/lib/types/types/class_of.rb +38 -0
  48. data/lib/types/types/enum.rb +42 -0
  49. data/lib/types/types/fixed_array.rb +60 -0
  50. data/lib/types/types/fixed_hash.rb +59 -0
  51. data/lib/types/types/intersection.rb +36 -0
  52. data/lib/types/types/noreturn.rb +25 -0
  53. data/lib/types/types/proc.rb +51 -0
  54. data/lib/types/types/self_type.rb +31 -0
  55. data/lib/types/types/simple.rb +33 -0
  56. data/lib/types/types/type_member.rb +7 -0
  57. data/lib/types/types/type_parameter.rb +23 -0
  58. data/lib/types/types/type_template.rb +7 -0
  59. data/lib/types/types/type_variable.rb +31 -0
  60. data/lib/types/types/typed_array.rb +20 -0
  61. data/lib/types/types/typed_enumerable.rb +141 -0
  62. data/lib/types/types/typed_enumerator.rb +22 -0
  63. data/lib/types/types/typed_hash.rb +29 -0
  64. data/lib/types/types/typed_range.rb +22 -0
  65. data/lib/types/types/typed_set.rb +22 -0
  66. data/lib/types/types/union.rb +59 -0
  67. data/lib/types/types/untyped.rb +25 -0
  68. data/lib/types/utils.rb +223 -0
  69. metadata +122 -15
@@ -0,0 +1,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