tapioca 0.4.27 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +15 -15
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +172 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
  13. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  14. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  15. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  16. data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
  17. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  18. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  19. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  20. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  21. data/lib/tapioca/compilers/dsl/active_support_concern.rb +106 -0
  22. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  23. data/lib/tapioca/compilers/dsl/base.rb +108 -82
  24. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  25. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  26. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  27. data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
  28. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  29. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  30. data/lib/tapioca/compilers/dsl/smart_properties.rb +21 -33
  31. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  32. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  33. data/lib/tapioca/compilers/dsl_compiler.rb +25 -40
  34. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
  35. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  36. data/lib/tapioca/compilers/sorbet.rb +25 -5
  37. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -206
  38. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  39. data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
  40. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  41. data/lib/tapioca/config.rb +3 -0
  42. data/lib/tapioca/config_builder.rb +5 -2
  43. data/lib/tapioca/constant_locator.rb +6 -8
  44. data/lib/tapioca/gemfile.rb +14 -11
  45. data/lib/tapioca/generators/base.rb +61 -0
  46. data/lib/tapioca/generators/dsl.rb +362 -0
  47. data/lib/tapioca/generators/gem.rb +345 -0
  48. data/lib/tapioca/generators/init.rb +79 -0
  49. data/lib/tapioca/generators/require.rb +52 -0
  50. data/lib/tapioca/generators/todo.rb +76 -0
  51. data/lib/tapioca/generators.rb +9 -0
  52. data/lib/tapioca/generic_type_registry.rb +25 -98
  53. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  54. data/lib/tapioca/internal.rb +2 -10
  55. data/lib/tapioca/loader.rb +11 -31
  56. data/lib/tapioca/rbi_ext/model.rb +166 -0
  57. data/lib/tapioca/reflection.rb +138 -0
  58. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  59. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  60. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  61. data/lib/tapioca/version.rb +1 -1
  62. data/lib/tapioca.rb +3 -0
  63. metadata +45 -23
  64. data/lib/tapioca/cli/main.rb +0 -146
  65. data/lib/tapioca/core_ext/class.rb +0 -28
  66. data/lib/tapioca/core_ext/string.rb +0 -18
  67. data/lib/tapioca/generator.rb +0 -633
  68. data/lib/tapioca/rbi/model.rb +0 -405
  69. data/lib/tapioca/rbi/printer.rb +0 -410
  70. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  71. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  72. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  73. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  74. data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -20,14 +20,15 @@ module Tapioca
20
20
  # variable to type variable serializers. This allows us to associate type variables
21
21
  # to the constant names that represent them, easily.
22
22
  module GenericTypeRegistry
23
+ TypeVariable = T.type_alias { T.any(TypeMember, TypeTemplate) }
23
24
  @generic_instances = T.let(
24
25
  {},
25
26
  T::Hash[String, Module]
26
27
  )
27
28
 
28
29
  @type_variables = T.let(
29
- {},
30
- T::Hash[Integer, T::Hash[Integer, String]]
30
+ {}.compare_by_identity,
31
+ T::Hash[Module, T::Hash[TypeVariable, String]]
31
32
  )
32
33
 
33
34
  class << self
@@ -49,7 +50,7 @@ module Tapioca
49
50
  # Build the name of the instantiated generic type,
50
51
  # something like `"Foo[X, Y, Z]"`
51
52
  type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
52
- name = "#{name_of(constant)}[#{type_list}]"
53
+ name = "#{Reflection.name_of(constant)}[#{type_list}]"
53
54
 
54
55
  # Create a generic type with an overridden `name`
55
56
  # method that returns the name we constructed above.
@@ -59,35 +60,30 @@ module Tapioca
59
60
  @generic_instances[name] ||= create_generic_type(constant, name)
60
61
  end
61
62
 
62
- sig do
63
- params(
64
- constant: T.untyped,
65
- type_member: T::Types::TypeVariable,
66
- fixed: T.untyped,
67
- lower: T.untyped,
68
- upper: T.untyped
69
- ).void
70
- end
71
- def register_type_member(constant, type_member, fixed, lower, upper)
72
- register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
63
+ sig { params(constant: Module).returns(T.nilable(T::Hash[TypeVariable, String])) }
64
+ def lookup_type_variables(constant)
65
+ @type_variables[constant]
73
66
  end
74
67
 
68
+ # This method is called from intercepted calls to `type_member` and `type_template`.
69
+ # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
70
+ # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
71
+ #
72
+ # This method creates a `String` with that data and stores it in the
73
+ # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
74
+ #
75
+ # Finally, the original `type_variable` is returned from this method, so that the caller
76
+ # can return it from the original methods as well.
75
77
  sig do
76
78
  params(
77
79
  constant: T.untyped,
78
- type_template: T::Types::TypeVariable,
79
- fixed: T.untyped,
80
- lower: T.untyped,
81
- upper: T.untyped
80
+ type_variable: TypeVariable,
82
81
  ).void
83
82
  end
84
- def register_type_template(constant, type_template, fixed, lower, upper)
85
- register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
86
- end
83
+ def register_type_variable(constant, type_variable)
84
+ type_variables = lookup_or_initialize_type_variables(constant)
87
85
 
88
- sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
89
- def lookup_type_variables(constant)
90
- @type_variables[object_id_of(constant)]
86
+ type_variables[type_variable] = type_variable.serialize
91
87
  end
92
88
 
93
89
  private
@@ -117,39 +113,6 @@ module Tapioca
117
113
  generic_type
118
114
  end
119
115
 
120
- # This method is called from intercepted calls to `type_member` and `type_template`.
121
- # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
122
- # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
123
- #
124
- # This method creates a `String` with that data and stores it in the
125
- # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
126
- #
127
- # Finally, the original `type_variable` is returned from this method, so that the caller
128
- # can return it from the original methods as well.
129
- sig do
130
- params(
131
- constant: T.untyped,
132
- type_variable_type: T.enum([:type_member, :type_template]),
133
- type_variable: T::Types::TypeVariable,
134
- fixed: T.untyped,
135
- lower: T.untyped,
136
- upper: T.untyped
137
- ).void
138
- end
139
- # rubocop:disable Metrics/ParameterLists
140
- def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
141
- # rubocop:enable Metrics/ParameterLists
142
- type_variables = lookup_or_initialize_type_variables(constant)
143
-
144
- type_variables[object_id_of(type_variable)] = serialize_type_variable(
145
- type_variable_type,
146
- type_variable.variance,
147
- fixed,
148
- lower,
149
- upper
150
- )
151
- end
152
-
153
116
  sig { params(constant: Class).returns(Class) }
154
117
  def create_safe_subclass(constant)
155
118
  # Lookup the "inherited" class method
@@ -164,11 +127,9 @@ module Tapioca
164
127
  # Otherwise, some inherited method could be preventing us
165
128
  # from creating subclasses, so let's override it and rescue
166
129
  owner.send(:define_method, :inherited) do |s|
167
- begin
168
- inherited_method.call(s)
169
- rescue
170
- # Ignoring errors
171
- end
130
+ inherited_method.call(s)
131
+ rescue
132
+ # Ignoring errors
172
133
  end
173
134
 
174
135
  # return a subclass
@@ -179,43 +140,9 @@ module Tapioca
179
140
  end
180
141
  end
181
142
 
182
- sig { params(constant: Module).returns(T::Hash[Integer, String]) }
143
+ sig { params(constant: Module).returns(T::Hash[TypeVariable, String]) }
183
144
  def lookup_or_initialize_type_variables(constant)
184
- @type_variables[object_id_of(constant)] ||= {}
185
- end
186
-
187
- sig do
188
- params(
189
- type_variable_type: Symbol,
190
- variance: Symbol,
191
- fixed: T.untyped,
192
- lower: T.untyped,
193
- upper: T.untyped
194
- ).returns(String)
195
- end
196
- def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
197
- parts = []
198
- parts << ":#{variance}" unless variance == :invariant
199
- parts << "fixed: #{fixed}" if fixed
200
- parts << "lower: #{lower}" unless lower == T.untyped
201
- parts << "upper: #{upper}" unless upper == BasicObject
202
-
203
- parameters = parts.join(", ")
204
-
205
- serialized = T.let(type_variable_type.to_s, String)
206
- serialized += "(#{parameters})" unless parameters.empty?
207
-
208
- serialized
209
- end
210
-
211
- sig { params(constant: Module).returns(T.nilable(String)) }
212
- def name_of(constant)
213
- Module.instance_method(:name).bind(constant).call
214
- end
215
-
216
- sig { params(object: BasicObject).returns(Integer) }
217
- def object_id_of(object)
218
- Object.instance_method(:object_id).bind(object).call
145
+ @type_variables[constant] ||= {}.compare_by_identity
219
146
  end
220
147
  end
221
148
  end
@@ -0,0 +1,98 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ class ActiveRecordColumnTypeHelper
5
+ extend T::Sig
6
+
7
+ sig { params(constant: T.class_of(ActiveRecord::Base)).void }
8
+ def initialize(constant)
9
+ @constant = constant
10
+ end
11
+
12
+ sig { params(column_name: String).returns([String, String]) }
13
+ def type_for(column_name)
14
+ return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
15
+
16
+ column_type = @constant.attribute_types[column_name]
17
+
18
+ getter_type =
19
+ case column_type
20
+ when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
21
+ "::Money"
22
+ when ActiveRecord::Type::Integer
23
+ "::Integer"
24
+ when ActiveRecord::Type::String
25
+ "::String"
26
+ when ActiveRecord::Type::Date
27
+ "::Date"
28
+ when ActiveRecord::Type::Decimal
29
+ "::BigDecimal"
30
+ when ActiveRecord::Type::Float
31
+ "::Float"
32
+ when ActiveRecord::Type::Boolean
33
+ "T::Boolean"
34
+ when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
35
+ "::DateTime"
36
+ when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
37
+ "::ActiveSupport::TimeWithZone"
38
+ else
39
+ handle_unknown_type(column_type)
40
+ end
41
+
42
+ column = @constant.columns_hash[column_name]
43
+ setter_type = getter_type
44
+
45
+ if column&.null
46
+ return ["T.nilable(#{getter_type})", "T.nilable(#{setter_type})"]
47
+ end
48
+
49
+ if column_name == @constant.primary_key ||
50
+ column_name == "created_at" ||
51
+ column_name == "updated_at"
52
+ getter_type = "T.nilable(#{getter_type})"
53
+ end
54
+
55
+ [getter_type, setter_type]
56
+ end
57
+
58
+ private
59
+
60
+ sig { params(constant: Module).returns(T::Boolean) }
61
+ def do_not_generate_strong_types?(constant)
62
+ Object.const_defined?(:StrongTypeGeneration) &&
63
+ !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
64
+ end
65
+
66
+ sig { params(column_type: Object).returns(String) }
67
+ def handle_unknown_type(column_type)
68
+ return "T.untyped" unless ActiveModel::Type::Value === column_type
69
+
70
+ lookup_return_type_of_method(column_type, :deserialize) ||
71
+ lookup_return_type_of_method(column_type, :cast) ||
72
+ lookup_arg_type_of_method(column_type, :serialize) ||
73
+ "T.untyped"
74
+ end
75
+
76
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
77
+ def lookup_return_type_of_method(column_type, method)
78
+ signature = T::Private::Methods.signature_for_method(column_type.method(method))
79
+ return unless signature
80
+
81
+ return_type = signature.return_type
82
+ return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
83
+
84
+ return_type.to_s
85
+ end
86
+
87
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
88
+ def lookup_arg_type_of_method(column_type, method)
89
+ signature = T::Private::Methods.signature_for_method(column_type.method(method))
90
+ return unless signature
91
+
92
+ # Arg types is an array [name, type] entries, so we desctructure the type of
93
+ # first argument to get the first argument type
94
+ _, first_argument_type = signature.arg_types.first
95
+
96
+ first_argument_type.to_s
97
+ end
98
+ end
@@ -3,22 +3,14 @@
3
3
 
4
4
  require "tapioca"
5
5
  require "tapioca/loader"
6
- require "tapioca/generic_type_registry"
7
6
  require "tapioca/sorbet_ext/generic_name_patch"
8
7
  require "tapioca/sorbet_ext/fixed_hash_patch"
8
+ require "tapioca/generic_type_registry"
9
9
  require "tapioca/config"
10
10
  require "tapioca/config_builder"
11
- require "tapioca/generator"
11
+ require "tapioca/generators"
12
12
  require "tapioca/cli"
13
- require "tapioca/cli/main"
14
13
  require "tapioca/gemfile"
15
- require "tapioca/rbi/model"
16
- require "tapioca/rbi/visitor"
17
- require "tapioca/rbi/rewriters/nest_singleton_methods"
18
- require "tapioca/rbi/rewriters/nest_non_public_methods"
19
- require "tapioca/rbi/rewriters/group_nodes"
20
- require "tapioca/rbi/rewriters/sort_nodes"
21
- require "tapioca/rbi/printer"
22
14
  require "tapioca/compilers/sorbet"
23
15
  require "tapioca/compilers/requires_compiler"
24
16
  require "tapioca/compilers/symbol_table_compiler"
@@ -5,13 +5,8 @@ module Tapioca
5
5
  class Loader
6
6
  extend(T::Sig)
7
7
 
8
- sig { params(gemfile: Tapioca::Gemfile).void }
9
- def initialize(gemfile)
10
- @gemfile = T.let(gemfile, Tapioca::Gemfile)
11
- end
12
-
13
- sig { params(initialize_file: T.nilable(String), require_file: T.nilable(String)).void }
14
- def load_bundle(initialize_file, require_file)
8
+ sig { params(gemfile: Tapioca::Gemfile, initialize_file: T.nilable(String), require_file: T.nilable(String)).void }
9
+ def load_bundle(gemfile, initialize_file, require_file)
15
10
  require_helper(initialize_file)
16
11
 
17
12
  load_rails_application
@@ -40,9 +35,6 @@ module Tapioca
40
35
 
41
36
  private
42
37
 
43
- sig { returns(Tapioca::Gemfile) }
44
- attr_reader :gemfile
45
-
46
38
  sig { params(file: T.nilable(String)).void }
47
39
  def require_helper(file)
48
40
  return unless file
@@ -54,18 +46,10 @@ module Tapioca
54
46
 
55
47
  sig { returns(T::Array[T.untyped]) }
56
48
  def rails_engines
57
- engines = []
58
-
59
- return engines unless Object.const_defined?("Rails::Engine")
60
-
61
- base = Object.const_get("Rails::Engine")
62
- ObjectSpace.each_object(base.singleton_class) do |k|
63
- k = T.cast(k, Class)
64
- next if k.singleton_class?
65
- engines.unshift(k) unless k == base
66
- end
49
+ return [] unless Object.const_defined?("Rails::Engine")
67
50
 
68
- engines.reject(&:abstract_railtie?)
51
+ # We can use `Class#descendants` here, since we know Rails is loaded
52
+ Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
69
53
  end
70
54
 
71
55
  sig { params(path: String).void }
@@ -116,22 +100,18 @@ module Tapioca
116
100
 
117
101
  engine.config.eager_load_paths.each do |load_path|
118
102
  Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
119
- begin
120
- require(file)
121
- rescue LoadError, StandardError
122
- errored_files << file
123
- end
103
+ require(file)
104
+ rescue LoadError, StandardError
105
+ errored_files << file
124
106
  end
125
107
  end
126
108
 
127
109
  # Try files that have errored one more time
128
110
  # It might have been a load order problem
129
111
  errored_files.each do |file|
130
- begin
131
- require(file)
132
- rescue LoadError, StandardError
133
- nil
134
- end
112
+ require(file)
113
+ rescue LoadError, StandardError
114
+ nil
135
115
  end
136
116
  end
137
117
  end
@@ -0,0 +1,166 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rbi"
5
+
6
+ module RBI
7
+ class File
8
+ extend T::Sig
9
+
10
+ sig { returns(String) }
11
+ def transformed_string
12
+ transform_rbi!
13
+ string
14
+ end
15
+
16
+ sig { void }
17
+ def transform_rbi!
18
+ root.nest_singleton_methods!
19
+ root.nest_non_public_methods!
20
+ root.group_nodes!
21
+ root.sort_nodes!
22
+ end
23
+
24
+ sig do
25
+ params(
26
+ command: String,
27
+ reason: T.nilable(String),
28
+ display_heading: T::Boolean
29
+ ).void
30
+ end
31
+ def set_file_header(command, reason: nil, display_heading: true)
32
+ return unless display_heading
33
+ comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
34
+ comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
35
+ comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
36
+ end
37
+
38
+ sig { void }
39
+ def set_empty_body_content
40
+ comments << RBI::EmptyComment.new unless comments.empty?
41
+ comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
42
+ comments << RBI::Comment.new("see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires")
43
+ end
44
+
45
+ sig { returns(T::Boolean) }
46
+ def empty?
47
+ root.empty?
48
+ end
49
+ end
50
+
51
+ class Tree
52
+ extend T::Sig
53
+
54
+ sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).void }
55
+ def create_path(constant, &block)
56
+ constant_name = Tapioca::Reflection.name_of(constant)
57
+ raise "given constant does not have a name" unless constant_name
58
+
59
+ instance = ::Module.const_get(constant_name)
60
+ case instance
61
+ when ::Class
62
+ create_class(constant.to_s, &block)
63
+ when ::Module
64
+ create_module(constant.to_s, &block)
65
+ else
66
+ raise "unexpected type: #{constant_name} is a #{instance.class}"
67
+ end
68
+ end
69
+
70
+ sig { params(name: String, block: T.nilable(T.proc.params(scope: Scope).void)).void }
71
+ def create_module(name, &block)
72
+ node = create_node(RBI::Module.new(name))
73
+ block&.call(T.cast(node, RBI::Scope))
74
+ end
75
+
76
+ sig do
77
+ params(
78
+ name: String,
79
+ superclass_name: T.nilable(String),
80
+ block: T.nilable(T.proc.params(scope: RBI::Scope).void)
81
+ ).void
82
+ end
83
+ def create_class(name, superclass_name: nil, &block)
84
+ node = create_node(RBI::Class.new(name, superclass_name: superclass_name))
85
+ block&.call(T.cast(node, RBI::Scope))
86
+ end
87
+
88
+ sig { params(name: String, value: String).void }
89
+ def create_constant(name, value:)
90
+ create_node(RBI::Const.new(name, value))
91
+ end
92
+
93
+ sig { params(name: String).void }
94
+ def create_include(name)
95
+ create_node(RBI::Include.new(name))
96
+ end
97
+
98
+ sig { params(name: String).void }
99
+ def create_extend(name)
100
+ create_node(RBI::Extend.new(name))
101
+ end
102
+
103
+ sig { params(name: String).void }
104
+ def create_mixes_in_class_methods(name)
105
+ create_node(RBI::MixesInClassMethods.new(name))
106
+ end
107
+
108
+ sig { params(name: String, value: String).void }
109
+ def create_type_member(name, value: "type_member")
110
+ create_node(RBI::TypeMember.new(name, value))
111
+ end
112
+
113
+ sig do
114
+ params(
115
+ name: String,
116
+ parameters: T::Array[TypedParam],
117
+ return_type: String,
118
+ class_method: T::Boolean
119
+ ).void
120
+ end
121
+ def create_method(name, parameters: [], return_type: "T.untyped", class_method: false)
122
+ return unless valid_method_name?(name)
123
+
124
+ sig = RBI::Sig.new(return_type: return_type)
125
+ method = RBI::Method.new(name, sigs: [sig], is_singleton: class_method)
126
+ parameters.each do |param|
127
+ method << param.param
128
+ sig << RBI::SigParam.new(param.param.name, param.type)
129
+ end
130
+ self << method
131
+ end
132
+
133
+ private
134
+
135
+ SPECIAL_METHOD_NAMES = T.let(
136
+ ["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<", "<=", "=>", ">", ">=",
137
+ "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"].freeze,
138
+ T::Array[String]
139
+ )
140
+
141
+ sig { params(name: String).returns(T::Boolean) }
142
+ def valid_method_name?(name)
143
+ return true if SPECIAL_METHOD_NAMES.include?(name)
144
+ !!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
145
+ end
146
+
147
+ sig { returns(T::Hash[String, RBI::Node]) }
148
+ def nodes_cache
149
+ T.must(@nodes_cache ||= T.let({}, T.nilable(T::Hash[String, Node])))
150
+ end
151
+
152
+ sig { params(node: RBI::Node).returns(RBI::Node) }
153
+ def create_node(node)
154
+ cached = nodes_cache[node.to_s]
155
+ return cached if cached
156
+ nodes_cache[node.to_s] = node
157
+ self << node
158
+ node
159
+ end
160
+ end
161
+
162
+ class TypedParam < T::Struct
163
+ const :param, RBI::Param
164
+ const :type, String
165
+ end
166
+ end
@@ -0,0 +1,138 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Reflection
6
+ extend T::Sig
7
+ extend self
8
+
9
+ CLASS_METHOD = T.let(Kernel.instance_method(:class), UnboundMethod)
10
+ CONSTANTS_METHOD = T.let(Module.instance_method(:constants), UnboundMethod)
11
+ NAME_METHOD = T.let(Module.instance_method(:name), UnboundMethod)
12
+ SINGLETON_CLASS_METHOD = T.let(Object.instance_method(:singleton_class), UnboundMethod)
13
+ ANCESTORS_METHOD = T.let(Module.instance_method(:ancestors), UnboundMethod)
14
+ SUPERCLASS_METHOD = T.let(Class.instance_method(:superclass), UnboundMethod)
15
+ OBJECT_ID_METHOD = T.let(BasicObject.instance_method(:__id__), UnboundMethod)
16
+ EQUAL_METHOD = T.let(BasicObject.instance_method(:equal?), UnboundMethod)
17
+ PUBLIC_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:public_instance_methods), UnboundMethod)
18
+ PROTECTED_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:protected_instance_methods), UnboundMethod)
19
+ PRIVATE_INSTANCE_METHODS_METHOD = T.let(Module.instance_method(:private_instance_methods), UnboundMethod)
20
+ METHOD_METHOD = T.let(Kernel.instance_method(:method), UnboundMethod)
21
+
22
+ sig { params(object: BasicObject).returns(Class).checked(:never) }
23
+ def class_of(object)
24
+ CLASS_METHOD.bind(object).call
25
+ end
26
+
27
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
28
+ def constants_of(constant)
29
+ CONSTANTS_METHOD.bind(constant).call(false)
30
+ end
31
+
32
+ sig { params(constant: Module).returns(T.nilable(String)) }
33
+ def name_of(constant)
34
+ name = NAME_METHOD.bind(constant).call
35
+ name&.start_with?("#<") ? nil : name
36
+ end
37
+
38
+ sig { params(constant: Module).returns(Class) }
39
+ def singleton_class_of(constant)
40
+ SINGLETON_CLASS_METHOD.bind(constant).call
41
+ end
42
+
43
+ sig { params(constant: Module).returns(T::Array[Module]) }
44
+ def ancestors_of(constant)
45
+ ANCESTORS_METHOD.bind(constant).call
46
+ end
47
+
48
+ sig { params(constant: Class).returns(T.nilable(Class)) }
49
+ def superclass_of(constant)
50
+ SUPERCLASS_METHOD.bind(constant).call
51
+ end
52
+
53
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
54
+ def object_id_of(object)
55
+ OBJECT_ID_METHOD.bind(object).call
56
+ end
57
+
58
+ sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
59
+ def are_equal?(object, other)
60
+ EQUAL_METHOD.bind(object).call(other)
61
+ end
62
+
63
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
64
+ def public_instance_methods_of(constant)
65
+ PUBLIC_INSTANCE_METHODS_METHOD.bind(constant).call
66
+ end
67
+
68
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
69
+ def protected_instance_methods_of(constant)
70
+ PROTECTED_INSTANCE_METHODS_METHOD.bind(constant).call
71
+ end
72
+
73
+ sig { params(constant: Module).returns(T::Array[Symbol]) }
74
+ def private_instance_methods_of(constant)
75
+ PRIVATE_INSTANCE_METHODS_METHOD.bind(constant).call
76
+ end
77
+
78
+ sig { params(constant: Module).returns(T::Array[Module]) }
79
+ def inherited_ancestors_of(constant)
80
+ if Class === constant
81
+ ancestors_of(superclass_of(constant) || Object)
82
+ else
83
+ Module.ancestors
84
+ end
85
+ end
86
+
87
+ sig { params(constant: Module).returns(T.nilable(String)) }
88
+ def qualified_name_of(constant)
89
+ name = name_of(constant)
90
+ return if name.nil?
91
+
92
+ if name.start_with?("::")
93
+ name
94
+ else
95
+ "::#{name}"
96
+ end
97
+ end
98
+
99
+ sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
100
+ def signature_of(method)
101
+ T::Private::Methods.signature_for_method(method)
102
+ rescue LoadError, StandardError
103
+ nil
104
+ end
105
+
106
+ sig { params(type: T::Types::Base).returns(String) }
107
+ def name_of_type(type)
108
+ type.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
109
+ end
110
+
111
+ sig { params(constant: Module, method: Symbol).returns(Method) }
112
+ def method_of(constant, method)
113
+ METHOD_METHOD.bind(constant).call(method)
114
+ end
115
+
116
+ # Returns an array with all classes that are < than the supplied class.
117
+ #
118
+ # class C; end
119
+ # descendants_of(C) # => []
120
+ #
121
+ # class B < C; end
122
+ # descendants_of(C) # => [B]
123
+ #
124
+ # class A < B; end
125
+ # descendants_of(C) # => [B, A]
126
+ #
127
+ # class D < C; end
128
+ # descendants_of(C) # => [B, A, D]
129
+ sig { type_parameters(:U).params(klass: T.type_parameter(:U)).returns(T::Array[T.type_parameter(:U)]) }
130
+ def descendants_of(klass)
131
+ result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
132
+ T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
133
+ end
134
+
135
+ T.unsafe(result)
136
+ end
137
+ end
138
+ end
@@ -13,7 +13,7 @@ module T
13
13
  end
14
14
  end
15
15
 
16
- "{#{entries.join(', ')}}"
16
+ "{#{entries.join(", ")}}"
17
17
  end
18
18
  end
19
19
  end