tapioca 0.7.3 → 0.8.0

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.
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