tapioca 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tapioca/cli.rb CHANGED
@@ -5,6 +5,7 @@ module Tapioca
5
5
  class Cli < Thor
6
6
  include CliHelper
7
7
  include ConfigHelper
8
+ include SorbetHelper
8
9
  include ShimsHelper
9
10
 
10
11
  FILE_HEADER_OPTION_DESC = "Add a \"This file is generated\" header on top of each generated RBI file"
@@ -89,7 +90,7 @@ module Tapioca
89
90
  option :quiet,
90
91
  aliases: ["-q"],
91
92
  type: :boolean,
92
- desc: "Supresses file creation output",
93
+ desc: "Suppresses file creation output",
93
94
  default: false
94
95
  option :workers,
95
96
  aliases: ["-w"],
@@ -171,7 +172,7 @@ module Tapioca
171
172
  option :doc,
172
173
  type: :boolean,
173
174
  desc: "Include YARD documentation from sources when generating RBIs. Warning: this might be slow",
174
- default: false
175
+ default: true
175
176
  option :exported_gem_rbis,
176
177
  type: :boolean,
177
178
  desc: "Include RBIs found in the `rbi/` directory of the gem",
@@ -242,6 +243,7 @@ module Tapioca
242
243
  option :gem_rbi_dir, type: :string, desc: "Path to gem RBIs", default: DEFAULT_GEM_DIR
243
244
  option :dsl_rbi_dir, type: :string, desc: "Path to DSL RBIs", default: DEFAULT_DSL_DIR
244
245
  option :shim_rbi_dir, type: :string, desc: "Path to shim RBIs", default: DEFAULT_SHIM_DIR
246
+ option :payload, type: :boolean, desc: "Check shims against Sorbet's payload", default: true
245
247
  def check_shims
246
248
  index = RBI::Index.new
247
249
 
@@ -251,6 +253,30 @@ module Tapioca
251
253
  exit(0)
252
254
  end
253
255
 
256
+ payload_path = T.let(nil, T.nilable(String))
257
+
258
+ if options[:payload]
259
+ if sorbet_supports?(:print_payload_sources)
260
+ Dir.mktmpdir do |dir|
261
+ payload_path = dir
262
+ result = sorbet("--no-config --print=payload-sources:#{payload_path}")
263
+
264
+ unless result.status
265
+ say_error("Sorbet failed to dump payload")
266
+ say_error(result.err)
267
+ exit(1)
268
+ end
269
+
270
+ index_payload(index, payload_path)
271
+ end
272
+ else
273
+ say_error("The version of Sorbet used in your Gemfile.lock does not support `--print=payload-sources`")
274
+ say_error("Current: v#{SORBET_GEM_SPEC.version}")
275
+ say_error("Required: #{FEATURE_REQUIREMENTS[:print_payload_sources]}")
276
+ exit(1)
277
+ end
278
+ end
279
+
254
280
  index_rbis(index, "shim", shim_rbi_dir)
255
281
  index_rbis(index, "gem", options[:gem_rbi_dir])
256
282
  index_rbis(index, "dsl", options[:dsl_rbi_dir])
@@ -260,7 +286,11 @@ module Tapioca
260
286
  duplicates.each do |key, nodes|
261
287
  say_error("\nDuplicated RBI for #{key}:", :red)
262
288
  nodes.each do |node|
263
- say_error(" * #{node.loc}", :red)
289
+ node_loc = node.loc
290
+ next unless node_loc
291
+
292
+ loc_string = location_to_payload_url(node_loc, path_prefix: payload_path)
293
+ say_error(" * #{loc_string}", :red)
264
294
  end
265
295
  end
266
296
  say_error("\nPlease remove the duplicated definitions from the #{shim_rbi_dir} directory.", :red)
@@ -271,6 +301,13 @@ module Tapioca
271
301
  exit(0)
272
302
  end
273
303
 
304
+ desc "annotations", "Pull gem annotations from a central RBI repository"
305
+ option :repo_uri, type: :string, desc: "Repository URI to pull annotations from", default: CENTRAL_REPO_ROOT_URI
306
+ def annotations
307
+ command = Commands::Annotations.new(central_repo_root_uri: options[:repo_uri])
308
+ command.execute
309
+ end
310
+
274
311
  map ["--version", "-v"] => :__print_version
275
312
 
276
313
  desc "--version, -v", "show version"
@@ -0,0 +1,154 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "net/http"
5
+
6
+ module Tapioca
7
+ module Commands
8
+ class Annotations < Command
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ central_repo_root_uri: String,
14
+ central_repo_index_path: String
15
+ ).void
16
+ end
17
+ def initialize(central_repo_root_uri:, central_repo_index_path: CENTRAL_REPO_INDEX_PATH)
18
+ super()
19
+ @central_repo_root_uri = central_repo_root_uri
20
+ @index = T.let(fetch_index, RepoIndex)
21
+ end
22
+
23
+ sig { override.void }
24
+ def execute
25
+ project_gems = list_gemfile_gems
26
+ remove_expired_annotations(project_gems)
27
+ fetch_annotations(project_gems)
28
+ end
29
+
30
+ private
31
+
32
+ sig { returns(T::Array[String]) }
33
+ def list_gemfile_gems
34
+ say("Listing gems from Gemfile.lock... ", [:blue, :bold])
35
+ gemfile = Bundler.read_file("Gemfile.lock")
36
+ parser = Bundler::LockfileParser.new(gemfile)
37
+ gem_names = parser.specs.map(&:name)
38
+ say("Done", :green)
39
+ gem_names
40
+ end
41
+
42
+ sig { params(project_gems: T::Array[String]).void }
43
+ def remove_expired_annotations(project_gems)
44
+ say("Removing annotations for gems that have been removed... ", [:blue, :bold])
45
+
46
+ annotations = Pathname.glob("#{DEFAULT_ANNOTATIONS_DIR}/*.rbi").map { |f| f.basename(".*").to_s }
47
+ expired = annotations - project_gems
48
+
49
+ if expired.empty?
50
+ say(" Nothing to do")
51
+ return
52
+ end
53
+
54
+ say("\n")
55
+ expired.each do |gem_name|
56
+ say("\n")
57
+ path = "#{DEFAULT_ANNOTATIONS_DIR}/#{gem_name}.rbi"
58
+ remove_file(path)
59
+ end
60
+ say("\nDone\n\n", :green)
61
+ end
62
+
63
+ sig { returns(RepoIndex) }
64
+ def fetch_index
65
+ say("Retrieving index from central repository... ", [:blue, :bold])
66
+ content = fetch_file(CENTRAL_REPO_INDEX_PATH)
67
+ exit(1) unless content
68
+
69
+ index = RepoIndex.from_json(content)
70
+ say("Done", :green)
71
+ index
72
+ end
73
+
74
+ sig { params(gem_names: T::Array[String]).returns(T::Array[String]) }
75
+ def fetch_annotations(gem_names)
76
+ say("Fetching gem annotations from central repository... ", [:blue, :bold])
77
+ fetchable_gems = gem_names.select { |gem_name| @index.has_gem?(gem_name) }
78
+
79
+ if fetchable_gems.empty?
80
+ say(" Nothing to do")
81
+ exit(0)
82
+ end
83
+
84
+ say("\n")
85
+ fetched_gems = fetchable_gems.select { |name| fetch_annotation(name) }
86
+ say("\nDone", :green)
87
+ fetched_gems
88
+ end
89
+
90
+ sig { params(gem_name: String).void }
91
+ def fetch_annotation(gem_name)
92
+ content = fetch_file("#{CENTRAL_REPO_ANNOTATIONS_DIR}/#{gem_name}.rbi")
93
+ return unless content
94
+
95
+ content = add_header(gem_name, content)
96
+
97
+ dir = DEFAULT_ANNOTATIONS_DIR
98
+ FileUtils.mkdir_p(dir)
99
+ say("\n Fetched #{set_color(gem_name, :yellow, :bold)}", :green)
100
+ create_file("#{dir}/#{gem_name}.rbi", content)
101
+ end
102
+
103
+ sig { params(path: String).returns(T.nilable(String)) }
104
+ def fetch_file(path)
105
+ if @central_repo_root_uri.start_with?(%r{https?://})
106
+ fetch_http_file(path)
107
+ else
108
+ fetch_local_file(path)
109
+ end
110
+ end
111
+
112
+ sig { params(path: String).returns(T.nilable(String)) }
113
+ def fetch_local_file(path)
114
+ File.read("#{@central_repo_root_uri}/#{path}")
115
+ rescue => e
116
+ say_error("\nCan't fetch file `#{path}` (#{e.message})", :bold, :red)
117
+ nil
118
+ end
119
+
120
+ sig { params(path: String).returns(T.nilable(String)) }
121
+ def fetch_http_file(path)
122
+ uri = URI("#{@central_repo_root_uri}/#{path}")
123
+ response = Net::HTTP.get_response(uri)
124
+ case response
125
+ when Net::HTTPSuccess
126
+ response.body
127
+ else
128
+ say_error("\nCan't fetch file `#{path}` from #{@central_repo_root_uri} (#{response.class})", :bold, :red)
129
+ nil
130
+ end
131
+ rescue SocketError, Errno::ECONNREFUSED => e
132
+ say_error("\nCan't fetch file `#{path}` from #{@central_repo_root_uri} (#{e.message})", :bold, :red)
133
+ nil
134
+ end
135
+
136
+ sig { params(name: String, content: String).returns(String) }
137
+ def add_header(name, content)
138
+ header = <<~COMMENT
139
+ # DO NOT EDIT MANUALLY
140
+ # This file was pulled from #{@central_repo_root_uri}.
141
+ # Please run `#{default_command(:annotations)}` to update it.
142
+ COMMENT
143
+
144
+ contents = content.split("\n")
145
+ if contents[0]&.start_with?("# typed:") && contents[1]&.empty?
146
+ contents.insert(2, header).join("\n")
147
+ else
148
+ say_error("Couldn't insert file header for content: #{content} due to unexpected file format")
149
+ content
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -4,6 +4,9 @@
4
4
  module Tapioca
5
5
  module Commands
6
6
  class Dsl < Command
7
+ include SorbetHelper
8
+ include RBIHelper
9
+
7
10
  sig do
8
11
  params(
9
12
  requested_constants: T::Array[String],
@@ -17,6 +20,8 @@ module Tapioca
17
20
  quiet: T::Boolean,
18
21
  verbose: T::Boolean,
19
22
  number_of_workers: T.nilable(Integer),
23
+ auto_strictness: T::Boolean,
24
+ gem_dir: String,
20
25
  rbi_formatter: RBIFormatter
21
26
  ).void
22
27
  end
@@ -32,6 +37,8 @@ module Tapioca
32
37
  quiet: false,
33
38
  verbose: false,
34
39
  number_of_workers: nil,
40
+ auto_strictness: true,
41
+ gem_dir: DEFAULT_GEM_DIR,
35
42
  rbi_formatter: DEFAULT_RBI_FORMATTER
36
43
  )
37
44
  @requested_constants = requested_constants
@@ -45,6 +52,8 @@ module Tapioca
45
52
  @quiet = quiet
46
53
  @verbose = verbose
47
54
  @number_of_workers = number_of_workers
55
+ @auto_strictness = auto_strictness
56
+ @gem_dir = gem_dir
48
57
  @rbi_formatter = rbi_formatter
49
58
 
50
59
  super()
@@ -102,9 +111,19 @@ module Tapioca
102
111
  perform_dsl_verification(outpath)
103
112
  else
104
113
  purge_stale_dsl_rbi_files(rbi_files_to_purge)
105
-
106
114
  say("Done", :green)
107
115
 
116
+ if @auto_strictness
117
+ say("")
118
+ validate_rbi_files(
119
+ command: default_command(:dsl, @requested_constants.join(" ")),
120
+ gem_dir: @gem_dir,
121
+ dsl_dir: @outpath.to_s,
122
+ auto_strictness: @auto_strictness,
123
+ compilers: pipeline.compilers
124
+ )
125
+ end
126
+
108
127
  say("All operations performed in working directory.", [:green, :bold])
109
128
  say("Please review changes and commit them.", [:green, :bold])
110
129
  end
@@ -5,6 +5,7 @@ module Tapioca
5
5
  module Commands
6
6
  class Gem < Command
7
7
  include SorbetHelper
8
+ include RBIHelper
8
9
 
9
10
  sig do
10
11
  params(
@@ -78,7 +79,13 @@ module Tapioca
78
79
  end
79
80
 
80
81
  if anything_done
81
- update_strictnesses(gem_queue.map(&:name), gem_dir: @outpath.to_s, dsl_dir: @dsl_dir) if @auto_strictness
82
+ validate_rbi_files(
83
+ command: default_command(:gem, @gem_names.join(" ")),
84
+ gem_dir: @outpath.to_s,
85
+ dsl_dir: @dsl_dir,
86
+ auto_strictness: @auto_strictness,
87
+ gems: bundle.dependencies
88
+ )
82
89
 
83
90
  say("All operations performed in working directory.", [:green, :bold])
84
91
  say("Please review changes and commit them.", [:green, :bold])
@@ -102,7 +109,13 @@ module Tapioca
102
109
  ].any?
103
110
 
104
111
  if anything_done
105
- update_strictnesses([], gem_dir: @outpath.to_s, dsl_dir: @dsl_dir) if @auto_strictness
112
+ validate_rbi_files(
113
+ command: default_command(:gem),
114
+ gem_dir: @outpath.to_s,
115
+ dsl_dir: @dsl_dir,
116
+ auto_strictness: @auto_strictness,
117
+ gems: bundle.dependencies
118
+ )
106
119
 
107
120
  say("All operations performed in working directory.", [:green, :bold])
108
121
  say("Please review changes and commit them.", [:green, :bold])
@@ -122,7 +135,7 @@ module Tapioca
122
135
 
123
136
  sig { returns(Gemfile) }
124
137
  def bundle
125
- @bundle ||= Gemfile.new
138
+ @bundle ||= Gemfile.new(@exclude)
126
139
  end
127
140
 
128
141
  sig { void }
@@ -359,6 +372,7 @@ module Tapioca
359
372
  sig { params(gem: Gemfile::GemSpec, file: RBI::File).void }
360
373
  def merge_with_exported_rbi(gem, file)
361
374
  return file unless gem.export_rbi_files?
375
+
362
376
  tree = gem.exported_rbi_tree
363
377
 
364
378
  unless tree.conflicts.empty?
@@ -379,60 +393,6 @@ module Tapioca
379
393
  say_error("\n\n RBIs exported by `#{gem.name}` contain errors and can't be used:", :yellow)
380
394
  say_error("Cause: #{e.message} (#{e.location})")
381
395
  end
382
-
383
- sig { params(gem_names: T::Array[String], gem_dir: String, dsl_dir: String).void }
384
- def update_strictnesses(gem_names, gem_dir: DEFAULT_GEM_DIR, dsl_dir: DEFAULT_DSL_DIR)
385
- return unless File.directory?(dsl_dir)
386
-
387
- error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
388
-
389
- say("Typechecking RBI files... ")
390
- res = sorbet(
391
- "--no-config",
392
- "--error-url-base=#{error_url_base}",
393
- "--isolate-error-code 4010",
394
- dsl_dir,
395
- gem_dir
396
- )
397
- say(" Done", :green)
398
-
399
- errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
400
-
401
- if errors.empty?
402
- say("No error found", [:green, :bold])
403
- return
404
- end
405
-
406
- files = []
407
-
408
- errors.each do |error|
409
- # Collect the file with error
410
- files << error.file
411
- error.more.each do |line|
412
- # Also collect the conflicting definition file paths
413
- next unless line.include?("Previous definition")
414
- files << line.split(":").first&.strip
415
- end
416
- end
417
-
418
- files
419
- .uniq
420
- .sort
421
- .select do |file|
422
- name = gem_name_from_rbi_path(file)
423
- file.start_with?(gem_dir) && (gem_names.empty? || gem_names.include?(name))
424
- end.each do |file|
425
- Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
426
- say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
427
- end
428
-
429
- say("\n")
430
- end
431
-
432
- sig { params(path: String).returns(String) }
433
- def gem_name_from_rbi_path(path)
434
- T.must(File.basename(path, ".rbi").split("@").first)
435
- end
436
396
  end
437
397
  end
438
398
  end
@@ -41,6 +41,7 @@ module Tapioca
41
41
  create_file(@sorbet_config, <<~CONTENT, skip: true, force: false)
42
42
  --dir
43
43
  .
44
+ --ignore=vendor/
44
45
  CONTENT
45
46
  end
46
47
 
@@ -4,6 +4,7 @@
4
4
  module Tapioca
5
5
  module Commands
6
6
  autoload :Command, "tapioca/commands/command"
7
+ autoload :Annotations, "tapioca/commands/annotations"
7
8
  autoload :Dsl, "tapioca/commands/dsl"
8
9
  autoload :Init, "tapioca/commands/init"
9
10
  autoload :Gem, "tapioca/commands/gem"
@@ -83,6 +83,7 @@ module Tapioca
83
83
  create_type_members(klass, "Key", "Value")
84
84
  else
85
85
  descriptor = T.let(T.unsafe(constant).descriptor, Google::Protobuf::Descriptor)
86
+ descriptor.each_oneof { |oneof| create_oneof_method(klass, oneof) }
86
87
  fields = descriptor.map { |desc| create_descriptor_method(klass, desc) }
87
88
  fields.sort_by!(&:name)
88
89
 
@@ -216,6 +217,19 @@ module Tapioca
216
217
 
217
218
  field
218
219
  end
220
+
221
+ sig do
222
+ params(
223
+ klass: RBI::Scope,
224
+ desc: Google::Protobuf::OneofDescriptor
225
+ ).void
226
+ end
227
+ def create_oneof_method(klass, desc)
228
+ klass.create_method(
229
+ desc.name,
230
+ return_type: "T.nilable(Symbol)"
231
+ )
232
+ end
219
233
  end
220
234
  end
221
235
  end
@@ -149,8 +149,12 @@ module Tapioca
149
149
 
150
150
  compilers.each do |compiler_class|
151
151
  next unless compiler_class.handles?(constant)
152
+
152
153
  compiler = compiler_class.new(self, file.root, constant)
153
154
  compiler.decorate
155
+ rescue
156
+ $stderr.puts("Error: `#{compiler_class.name}` failed to generate RBI for `#{constant}`")
157
+ raise # This is an unexpected error, so re-raise it
154
158
  end
155
159
 
156
160
  return if file.root.empty?
@@ -0,0 +1,6 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca"
5
+ require "tapioca/runtime/reflection"
6
+ require "tapioca/dsl/compiler"
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "etc"
5
+ require "parallel"
5
6
 
6
7
  module Tapioca
7
8
  class Executor
@@ -20,10 +21,6 @@ module Tapioca
20
21
  number_of_workers || [Etc.nprocessors, (queue.length.to_f / MINIMUM_ITEMS_PER_WORKER).ceil].min,
21
22
  Integer
22
23
  )
23
-
24
- # The number of items that will be processed per worker, so that we can split the queue into groups and assign
25
- # them to each one of the workers
26
- @items_per_worker = T.let((queue.length.to_f / @number_of_workers).ceil, Integer)
27
24
  end
28
25
 
29
26
  sig do
@@ -32,48 +29,9 @@ module Tapioca
32
29
  ).returns(T::Array[T.type_parameter(:T)])
33
30
  end
34
31
  def run_in_parallel(&block)
35
- # If we only have one worker selected, it's not worth forking, just run sequentially
36
- return @queue.map { |item| block.call(item) } if @number_of_workers == 1
37
-
38
- read_pipes = []
39
- write_pipes = []
40
-
41
- # If we have more than one worker, fork the pool by shifting the expected number of items per worker from the
42
- # queue
43
- workers = (0...@number_of_workers).map do
44
- items = @queue.shift(@items_per_worker)
45
-
46
- # Each worker has their own pair of pipes, so that we can read the result from each worker separately
47
- read, write = IO.pipe
48
- read_pipes << read
49
- write_pipes << write
50
-
51
- fork do
52
- read.close
53
- result = items.map { |item| block.call(item) }
54
-
55
- # Pack the result as a Base64 string of the Marshal dump of the array of values returned by the block that we
56
- # ran in parallel
57
- packed = [Marshal.dump(result)].pack("m")
58
- write.puts(packed)
59
- write.close
60
- end
61
- end
62
-
63
- # Close all the write pipes, then read and close from all the read pipes
64
- write_pipes.each(&:close)
65
- result = read_pipes.map do |pipe|
66
- content = pipe.read
67
- pipe.close
68
- content
69
- end
70
-
71
- # Wait until all the workers finish. Notice that waiting for the PIDs can only happen after we read and close the
72
- # pipe or else we may end up in a condition where writing to the pipe hangs indefinitely
73
- workers.each { |pid| Process.waitpid(pid) }
74
-
75
- # Decode the value back into the Ruby objects by doing the inverse of what each worker does
76
- result.flat_map { |item| T.unsafe(Marshal.load(item.unpack1("m"))) }
32
+ # To have the parallel gem run jobs in the parent process, you must pass 0 as the number of processes
33
+ number_of_processes = @number_of_workers == 1 ? 0 : @number_of_workers
34
+ Parallel.map(@queue, { in_processes: number_of_processes }, &block)
77
35
  end
78
36
  end
79
37
  end
@@ -41,6 +41,7 @@ module Tapioca
41
41
  .each do |visibility, method_list|
42
42
  method_list.sort!.map do |name|
43
43
  next if name == :initialize
44
+
44
45
  vis = case visibility
45
46
  when :protected
46
47
  RBI::Protected.new
@@ -65,7 +66,7 @@ module Tapioca
65
66
  end
66
67
  def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
67
68
  return unless method
68
- return unless method.owner == constant
69
+ return unless method_owned_by_constant?(method, constant)
69
70
  return if @pipeline.symbol_in_payload?(symbol_name) && !@pipeline.method_in_gem?(method)
70
71
 
71
72
  signature = signature_of(method)
@@ -141,6 +142,29 @@ module Tapioca
141
142
  tree << rbi_method
142
143
  end
143
144
 
145
+ # Check whether the method is defined by the constant.
146
+ #
147
+ # In most cases, it works to check that the constant is the method owner. However,
148
+ # in the case that a method is also defined in a module prepended to the constant, it
149
+ # will be owned by the prepended module, not the constant.
150
+ #
151
+ # This method implements a better way of checking whether a constant defines a method.
152
+ # It walks up the ancestor tree via the `super_method` method; if any of the super
153
+ # methods are owned by the constant, it means that the constant declares the method.
154
+ sig { params(method: UnboundMethod, constant: Module).returns(T::Boolean) }
155
+ def method_owned_by_constant?(method, constant)
156
+ # Widen the type of `method` to be nilable
157
+ method = T.let(method, T.nilable(UnboundMethod))
158
+
159
+ while method
160
+ return true if method.owner == constant
161
+
162
+ method = method.super_method
163
+ end
164
+
165
+ false
166
+ end
167
+
144
168
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
145
169
  def method_names_by_visibility(mod)
146
170
  {
@@ -163,6 +187,7 @@ module Tapioca
163
187
  sig { params(name: String).returns(T::Boolean) }
164
188
  def valid_method_name?(name)
165
189
  return true if SPECIAL_METHOD_NAMES.include?(name)
190
+
166
191
  !!name.match(/^[[:word:]]+[?!=]?$/)
167
192
  end
168
193
 
@@ -19,7 +19,7 @@ module Tapioca
19
19
  constant.props.map do |name, prop|
20
20
  type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
21
21
 
22
- default = prop.key?(:default) ? "T.unsafe(nil)" : nil
22
+ default = prop.key?(:default) || prop.key?(:factory) ? "T.unsafe(nil)" : nil
23
23
  node << if prop.fetch(:immutable, false)
24
24
  RBI::TStructConst.new(name.to_s, type, default: default)
25
25
  else
@@ -14,6 +14,7 @@ module Tapioca
14
14
  ancestors = Runtime::Trackers::RequiredAncestor.required_ancestors_by(event.constant)
15
15
  ancestors.each do |ancestor|
16
16
  next unless ancestor # TODO: We should have a way to warn from here
17
+
17
18
  event.node << RBI::RequiresAncestor.new(ancestor.to_s)
18
19
  end
19
20
  end
@@ -87,6 +87,7 @@ module Tapioca
87
87
  def symbol_in_payload?(symbol_name)
88
88
  symbol_name = symbol_name[2..-1] if symbol_name.start_with?("::")
89
89
  return false unless symbol_name
90
+
90
91
  @payload_symbols.include?(symbol_name)
91
92
  end
92
93
 
@@ -102,9 +103,11 @@ module Tapioca
102
103
  def name_of(constant)
103
104
  name = name_of_proxy_target(constant, super(class_of(constant)))
104
105
  return name if name
106
+
105
107
  name = super(constant)
106
108
  return if name.nil?
107
109
  return unless are_equal?(constant, constantize(name, inherit: true))
110
+
108
111
  name = "Struct" if name =~ /^(::)?Struct::[^:]+$/
109
112
  name
110
113
  end
@@ -350,6 +353,7 @@ module Tapioca
350
353
  sig { params(constant: Module, class_name: T.nilable(String)).returns(T.nilable(String)) }
351
354
  def name_of_proxy_target(constant, class_name)
352
355
  return unless class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy"
356
+
353
357
  # We are dealing with a ActiveSupport::Deprecation::DeprecatedConstantProxy
354
358
  # so try to get the name of the target class
355
359
  begin