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,111 @@
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 {params(key: Symbol).returns(T::Boolean).checked(:never)}
15
+ def valid_rule_key?(key)
16
+ super || :DEPRECATED_underspecified_type == key
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]
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
+ #
57
+ # checked(:never) - called potentially many times recursively
58
+ sig {params(type: T::Types::Base).returns(T.nilable(T::Types::Base)).checked(:never)}
59
+ private def find_invalid_subtype(type)
60
+ case type
61
+ when T::Types::TypedEnumerable
62
+ find_invalid_subtype(type.type)
63
+ when T::Types::FixedHash
64
+ type.types.values.map {|subtype| find_invalid_subtype(subtype)}.compact.first
65
+ when T::Types::Union, T::Types::FixedArray
66
+ # `T.any` is valid if all of the members are valid
67
+ type.types.map {|subtype| find_invalid_subtype(subtype)}.compact.first
68
+ when T::Types::Intersection
69
+ # `T.all` is valid if at least one of the members is valid
70
+ invalid = type.types.map {|subtype| find_invalid_subtype(subtype)}.compact
71
+ if invalid.length == type.types.length
72
+ invalid.first
73
+ else
74
+ nil
75
+ end
76
+ when T::Types::Enum, T::Types::ClassOf
77
+ nil
78
+ when T::Private::Types::TypeAlias
79
+ find_invalid_subtype(type.aliased_type)
80
+ when T::Types::Simple
81
+ # TODO Could we manage to define a whitelist, consisting of something
82
+ # like primitives, subdocs, DataInterfaces, and collections/enums/unions
83
+ # thereof?
84
+ if BANNED_TYPES.include?(type.raw_type)
85
+ type
86
+ else
87
+ nil
88
+ end
89
+ else
90
+ type
91
+ end
92
+ end
93
+
94
+ sig do
95
+ params(
96
+ type: T::Types::Base,
97
+ field_name: T.any(Symbol, String),
98
+ orig_type: T::Types::Base,
99
+ )
100
+ .returns(String)
101
+ end
102
+ private def type_error_message(type, field_name, orig_type)
103
+ msg_prefix = "#{@class.name}.#{field_name}: #{orig_type} is invalid in prop definition"
104
+ if type == orig_type
105
+ "#{msg_prefix}. Please choose a more specific type (T.untyped and ~equivalents like Object are banned)."
106
+ else
107
+ "#{msg_prefix}. Please choose a subtype more specific than #{type} (T.untyped and ~equivalents like Object are banned)."
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,59 @@
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
+ when T::Enum
27
+ what
28
+ else
29
+ what.clone
30
+ end
31
+ freeze ? result.freeze : result
32
+ end
33
+
34
+ # The prop_rules indicate whether we should check for reading a nil value for the prop/field.
35
+ # This is mostly for the compatibility check that we allow existing documents carry some nil prop/field.
36
+ def self.need_nil_read_check?(prop_rules)
37
+ # . :on_load allows nil read, but we need to check for the read for future writes
38
+ prop_rules[:optional] == :on_load || prop_rules[:raise_on_nil_write]
39
+ end
40
+
41
+ # The prop_rules indicate whether we should check for writing a nil value for the prop/field.
42
+ def self.need_nil_write_check?(prop_rules)
43
+ need_nil_read_check?(prop_rules) || T::Props::Utils.required_prop?(prop_rules)
44
+ end
45
+
46
+ def self.required_prop?(prop_rules)
47
+ # Clients should never reference :_tnilable as the implementation can change.
48
+ !prop_rules[:_tnilable]
49
+ end
50
+
51
+ def self.optional_prop?(prop_rules)
52
+ # Clients should never reference :_tnilable as the implementation can change.
53
+ !!prop_rules[:_tnilable]
54
+ end
55
+
56
+ def self.merge_serialized_optional_rule(prop_rules)
57
+ {'_tnilable' => true}.merge(prop_rules.merge('_tnilable' => true))
58
+ end
59
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::WeakConstructor
5
+ include T::Props::Optional
6
+ extend T::Sig
7
+
8
+ # checked(:never) - O(runtime object construction)
9
+ sig {params(hash: T::Hash[Symbol, T.untyped]).void.checked(:never)}
10
+ def initialize(hash={})
11
+ decorator = self.class.decorator
12
+
13
+ hash_keys_matching_props = decorator.construct_props_with_defaults(self, hash) +
14
+ decorator.construct_props_without_defaults(self, hash)
15
+
16
+ if hash_keys_matching_props < hash.size
17
+ raise ArgumentError.new("#{self.class}: Unrecognized properties: #{(hash.keys - decorator.props.keys).join(', ')}")
18
+ end
19
+ end
20
+ end
21
+
22
+ module T::Props::WeakConstructor::DecoratorMethods
23
+ extend T::Sig
24
+
25
+ # Set values for all props that have no defaults. Ignore any not present.
26
+ #
27
+ # @return [Integer] A count of props that we successfully initialized (which
28
+ # we'll use to check for any unrecognized input.)
29
+ #
30
+ # checked(:never) - O(runtime object construction)
31
+ sig {params(instance: T::Props::WeakConstructor, hash: T::Hash[Symbol, T.untyped]).returns(Integer).checked(:never)}
32
+ def construct_props_without_defaults(instance, hash)
33
+ # Use `each_pair` rather than `count` because, as of Ruby 2.6, the latter delegates to Enumerator
34
+ # and therefore allocates for each entry.
35
+ result = 0
36
+ @props_without_defaults&.each_pair do |p, setter_proc|
37
+ if hash.key?(p)
38
+ instance.instance_exec(hash[p], &setter_proc)
39
+ result += 1
40
+ end
41
+ end
42
+ result
43
+ end
44
+
45
+ # Set values for all props that have defaults. Use the default if and only if
46
+ # the prop key isn't in the input.
47
+ #
48
+ # @return [Integer] A count of props that we successfully initialized (which
49
+ # we'll use to check for any unrecognized input.)
50
+ #
51
+ # checked(:never) - O(runtime object construction)
52
+ sig {params(instance: T::Props::WeakConstructor, hash: T::Hash[Symbol, T.untyped]).returns(Integer).checked(:never)}
53
+ def construct_props_with_defaults(instance, hash)
54
+ # Use `each_pair` rather than `count` because, as of Ruby 2.6, the latter delegates to Enumerator
55
+ # and therefore allocates for each entry.
56
+ result = 0
57
+ @props_with_defaults&.each_pair do |p, default_struct|
58
+ if hash.key?(p)
59
+ instance.instance_exec(hash[p], &default_struct.setter_proc)
60
+ result += 1
61
+ else
62
+ default_struct.set_default(instance)
63
+ end
64
+ end
65
+ result
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # From the static system, T::Utils::RuntimeProfiled is T.untyped.
6
+ #
7
+ # But from the runtime system, it's a random class (specifically, a class that
8
+ # normal programs currently don't have any instances of).
9
+ #
10
+ # Thus, T::Utils::RuntimeProfiled can be used to introduce runtime-only type
11
+ # errors. This seems like a bad idea, but it's not. It can be used to gather
12
+ # runtime type information from running code via a custom T::Configuration
13
+ # handler.
14
+ #
15
+ # This process has only ever been used at Stripe, and is likely to have rough
16
+ # edges. If you've managed to find your way here and you're curious to try it,
17
+ # please chat with us on Slack. There are no docs.
18
+ #
19
+ # See also: the --suggest-runtime-profiled flag to sorbet.
20
+ #
21
+
22
+ module T; end
23
+ module T::Utils; end
24
+ class T::Utils::RuntimeProfiled; end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ # Used as a mixin to any class so that you can call `sig`.
5
+ # Docs at https://sorbet.org/docs/sigs
6
+ module T::Sig
7
+ module WithoutRuntime
8
+ # At runtime, does nothing, but statically it is treated exactly the same
9
+ # as T::Sig#sig. Only use it in cases where you can't use T::Sig#sig.
10
+ def self.sig(arg0=nil, &blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
11
+
12
+ original_verbose = $VERBOSE
13
+ $VERBOSE = false
14
+
15
+ # At runtime, does nothing, but statically it is treated exactly the same
16
+ # as T::Sig#sig. Only use it in cases where you can't use T::Sig#sig.
17
+ T::Sig::WithoutRuntime.sig {params(arg0: T.nilable(Symbol), blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
18
+ def self.sig(arg0=nil, &blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride, Lint/DuplicateMethods
19
+
20
+ $VERBOSE = original_verbose
21
+ end
22
+
23
+ # Declares a method with type signatures and/or
24
+ # abstract/override/... helpers. See the documentation URL on
25
+ # {T::Helpers}
26
+ T::Sig::WithoutRuntime.sig {params(arg0: T.nilable(Symbol), blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
27
+ def sig(arg0=nil, &blk) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
28
+ T::Private::Methods.declare_sig(self, arg0, &blk)
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ class T::InexactStruct
5
+ include T::Props
6
+ include T::Props::Serializable
7
+ include T::Props::Constructor
8
+ end
9
+
10
+ class T::Struct < T::InexactStruct
11
+ def self.inherited(subclass)
12
+ super(subclass)
13
+ T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited) do |s|
14
+ super(s)
15
+ raise "#{self.name} is a subclass of T::Struct and cannot be subclassed"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Types
5
+ # Modeling AttachedClass properly at runtime would require additional
6
+ # tracking, so at runtime we permit all values and rely on the static checker.
7
+ # As AttachedClass is modeled statically as a type member on every singleton
8
+ # class, this is consistent with the runtime behavior for all type members.
9
+ class AttachedClassType < Base
10
+
11
+ def initialize(); end
12
+
13
+ # @override Base
14
+ def name
15
+ "AttachedClass"
16
+ end
17
+
18
+ # @override Base
19
+ def valid?(obj)
20
+ true
21
+ end
22
+
23
+ # @override Base
24
+ private def subtype_of_single?(other)
25
+ case other
26
+ when AttachedClassType
27
+ true
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ module Private
34
+ INSTANCE = AttachedClassType.new.freeze
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Types
5
+ class Base
6
+ def self.method_added(method_name)
7
+ super(method_name)
8
+ # What is now `subtype_of_single?` used to be named `subtype_of?`. Make sure people don't
9
+ # override the wrong thing.
10
+ #
11
+ # NB: Outside of T::Types, we would enforce this by using `sig` and not declaring the method
12
+ # as overridable, but doing so here would result in a dependency cycle.
13
+ if method_name == :subtype_of? && self != T::Types::Base
14
+ raise "`subtype_of?` should not be overridden. You probably want to override " \
15
+ "`subtype_of_single?` instead."
16
+ end
17
+ end
18
+
19
+ def valid?(obj)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ # @return [T::Boolean] This method must be implemented to return whether the subclass is a subtype
24
+ # of `type`. This should only be called by `subtype_of?`, which guarantees that `type` will be
25
+ # a "single" type, by which we mean it won't be a Union or an Intersection (c.f.
26
+ # `isSubTypeSingle` in sorbet).
27
+ private def subtype_of_single?(type)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ # Equality is based on name, so be sure the name reflects all relevant state when implementing.
32
+ def name
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # Mirrors ruby_typer::core::Types::isSubType
37
+ # See https://git.corp.stripe.com/stripe-internal/ruby-typer/blob/9fc8ed998c04ac0b96592ae6bb3493b8a925c5c1/core/types/subtyping.cc#L912-L950
38
+ #
39
+ # This method cannot be overridden (see `method_added` above).
40
+ # Subclasses only need to implement `subtype_of_single?`).
41
+ def subtype_of?(t2)
42
+ t1 = self
43
+
44
+ if t2.is_a?(T::Private::Types::TypeAlias)
45
+ t2 = t2.aliased_type
46
+ end
47
+
48
+ if t1.is_a?(T::Private::Types::TypeAlias)
49
+ return t1.aliased_type.subtype_of?(t2)
50
+ end
51
+
52
+ # pairs to cover: 1 (_, _)
53
+ # 2 (_, And)
54
+ # 3 (_, Or)
55
+ # 4 (And, _)
56
+ # 5 (And, And)
57
+ # 6 (And, Or)
58
+ # 7 (Or, _)
59
+ # 8 (Or, And)
60
+ # 9 (Or, Or)
61
+
62
+ # Note: order of cases here matters!
63
+ if t1.is_a?(T::Types::Union) # 7, 8, 9
64
+ # this will be incorrect if/when we have Type members
65
+ return t1.types.all? {|t1_member| t1_member.subtype_of?(t2)}
66
+ end
67
+
68
+ if t2.is_a?(T::Types::Intersection) # 2, 5
69
+ # this will be incorrect if/when we have Type members
70
+ return t2.types.all? {|t2_member| t1.subtype_of?(t2_member)}
71
+ end
72
+
73
+ if t2.is_a?(T::Types::Union)
74
+ if t1.is_a?(T::Types::Intersection) # 6
75
+ # dropping either of parts eagerly make subtype test be too strict.
76
+ # we have to try both cases, when we normally try only one
77
+ return t2.types.any? {|t2_member| t1.subtype_of?(t2_member)} ||
78
+ t1.types.any? {|t1_member| t1_member.subtype_of?(t2)}
79
+ end
80
+ return t2.types.any? {|t2_member| t1.subtype_of?(t2_member)} # 3
81
+ end
82
+
83
+ if t1.is_a?(T::Types::Intersection) # 4
84
+ # this will be incorrect if/when we have Type members
85
+ return t1.types.any? {|t1_member| t1_member.subtype_of?(t2)}
86
+ end
87
+
88
+ # 1; Start with some special cases
89
+ if t1.is_a?(T::Private::Types::Void)
90
+ return t2.is_a?(T::Private::Types::Void)
91
+ end
92
+
93
+ if t1.is_a?(T::Types::Untyped) || t2.is_a?(T::Types::Untyped)
94
+ return true
95
+ end
96
+
97
+ # Rest of (1)
98
+ subtype_of_single?(t2)
99
+ end
100
+
101
+ def to_s
102
+ name
103
+ end
104
+
105
+ def describe_obj(obj)
106
+ # Would be redundant to print class and value in these common cases.
107
+ case obj
108
+ when nil, true, false
109
+ return "type #{obj.class}"
110
+ end
111
+
112
+ # In rare cases, obj.inspect may fail, or be undefined, so rescue.
113
+ begin
114
+ # Default inspect behavior of, eg; `#<Object:0x0...>` is ugly; just print the hash instead, which is more concise/readable.
115
+ if obj.method(:inspect).owner == Kernel
116
+ "type #{obj.class} with hash #{obj.hash}"
117
+ else
118
+ "type #{obj.class} with value #{T::Utils.string_truncate_middle(obj.inspect, 30, 30)}"
119
+ end
120
+ rescue StandardError, SystemStackError
121
+ "type #{obj.class} with unprintable value"
122
+ end
123
+ end
124
+
125
+ def error_message_for_obj(obj)
126
+ if valid?(obj)
127
+ nil
128
+ else
129
+ "Expected type #{self.name}, got #{describe_obj(obj)}"
130
+ end
131
+ end
132
+
133
+ def validate!(obj)
134
+ err = error_message_for_obj(obj)
135
+ raise TypeError.new(err) if err
136
+ end
137
+
138
+ ### Equality methods (necessary for deduping types with `uniq`)
139
+
140
+ def hash
141
+ name.hash
142
+ end
143
+
144
+ def ==(other)
145
+ (T::Utils.resolve_alias(other).class == T::Utils.resolve_alias(self).class) &&
146
+ other.name == self.name
147
+ end
148
+
149
+ alias_method :eql?, :==
150
+ end
151
+ end