tapioca 0.3.1 → 0.4.4

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +25 -1
  3. data/README.md +23 -2
  4. data/Rakefile +15 -4
  5. data/lib/tapioca.rb +8 -2
  6. data/lib/tapioca/cli.rb +32 -3
  7. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +129 -0
  8. data/lib/tapioca/compilers/dsl/action_mailer.rb +65 -0
  9. data/lib/tapioca/compilers/dsl/active_record_associations.rb +267 -0
  10. data/lib/tapioca/compilers/dsl/active_record_columns.rb +393 -0
  11. data/lib/tapioca/compilers/dsl/active_record_enum.rb +112 -0
  12. data/lib/tapioca/compilers/dsl/active_record_identity_cache.rb +213 -0
  13. data/lib/tapioca/compilers/dsl/active_record_scope.rb +100 -0
  14. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +170 -0
  15. data/lib/tapioca/compilers/dsl/active_resource.rb +140 -0
  16. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +126 -0
  17. data/lib/tapioca/compilers/dsl/base.rb +165 -0
  18. data/lib/tapioca/compilers/dsl/frozen_record.rb +96 -0
  19. data/lib/tapioca/compilers/dsl/protobuf.rb +144 -0
  20. data/lib/tapioca/compilers/dsl/smart_properties.rb +173 -0
  21. data/lib/tapioca/compilers/dsl/state_machines.rb +378 -0
  22. data/lib/tapioca/compilers/dsl/url_helpers.rb +92 -0
  23. data/lib/tapioca/compilers/dsl_compiler.rb +121 -0
  24. data/lib/tapioca/compilers/requires_compiler.rb +67 -0
  25. data/lib/tapioca/compilers/sorbet.rb +34 -0
  26. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +171 -26
  27. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +1 -20
  28. data/lib/tapioca/compilers/todos_compiler.rb +32 -0
  29. data/lib/tapioca/config.rb +14 -6
  30. data/lib/tapioca/config_builder.rb +22 -9
  31. data/lib/tapioca/constant_locator.rb +1 -0
  32. data/lib/tapioca/core_ext/class.rb +23 -0
  33. data/lib/tapioca/gemfile.rb +32 -9
  34. data/lib/tapioca/generator.rb +231 -23
  35. data/lib/tapioca/loader.rb +30 -9
  36. data/lib/tapioca/version.rb +1 -1
  37. metadata +32 -39
@@ -0,0 +1,92 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "parlour"
5
+
6
+ begin
7
+ require "rails"
8
+ require "action_controller"
9
+ require "action_view"
10
+ rescue LoadError
11
+ return
12
+ end
13
+
14
+ module Tapioca
15
+ module Compilers
16
+ module Dsl
17
+ class UrlHelpers < Base
18
+ extend T::Sig
19
+
20
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
21
+ def decorate(root, constant)
22
+ case constant
23
+ when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
24
+ generate_module_for(root, constant)
25
+ else
26
+ root.path(constant) do |mod|
27
+ create_mixins_for(mod, constant, GeneratedUrlHelpersModule)
28
+ create_mixins_for(mod, constant, GeneratedPathHelpersModule)
29
+ end
30
+ end
31
+ end
32
+
33
+ NON_DISCOVERABLE_INCLUDERS = T.let([
34
+ ActionDispatch::IntegrationTest,
35
+ ActionView::Helpers,
36
+ ], T::Array[Module])
37
+
38
+ sig { override.returns(T::Enumerable[Module]) }
39
+ def gather_constants
40
+ Object.const_set(:GeneratedUrlHelpersModule, Rails.application.routes.named_routes.url_helpers_module)
41
+ Object.const_set(:GeneratedPathHelpersModule, Rails.application.routes.named_routes.path_helpers_module)
42
+
43
+ module_enumerator = T.cast(ObjectSpace.each_object(Module), T::Enumerator[Module])
44
+ constants = module_enumerator.select do |mod|
45
+ next unless Module.instance_method(:name).bind(mod).call
46
+
47
+ includes_helper?(mod, GeneratedUrlHelpersModule) ||
48
+ includes_helper?(mod, GeneratedPathHelpersModule) ||
49
+ includes_helper?(mod.singleton_class, GeneratedUrlHelpersModule) ||
50
+ includes_helper?(mod.singleton_class, GeneratedPathHelpersModule)
51
+ end
52
+
53
+ constants.concat(NON_DISCOVERABLE_INCLUDERS)
54
+ end
55
+
56
+ private
57
+
58
+ sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
59
+ def generate_module_for(root, constant)
60
+ root.create_module(T.must(constant.name)) do |mod|
61
+ mod.create_include("ActionDispatch::Routing::UrlFor")
62
+ mod.create_include("ActionDispatch::Routing::PolymorphicRoutes")
63
+
64
+ constant.instance_methods(false).each do |method|
65
+ mod.create_method(
66
+ method.to_s,
67
+ parameters: [Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped")],
68
+ return_type: "String"
69
+ )
70
+ end
71
+ end
72
+ end
73
+
74
+ sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module), helper_module: Module).void }
75
+ def create_mixins_for(mod, constant, helper_module)
76
+ include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
77
+ extend_helper = constant.singleton_class.ancestors.include?(helper_module)
78
+
79
+ mod.create_include(T.must(helper_module.name)) if include_helper
80
+ mod.create_extend(T.must(helper_module.name)) if extend_helper
81
+ end
82
+
83
+ sig { params(mod: Module, helper: Module).returns(T::Boolean) }
84
+ def includes_helper?(mod, helper)
85
+ superclass_ancestors = mod.superclass&.ancestors if Class === mod
86
+ superclass_ancestors ||= []
87
+ (mod.ancestors - superclass_ancestors).include?(helper)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require "tapioca/compilers/dsl/base"
5
+
6
+ module Tapioca
7
+ module Compilers
8
+ class DslCompiler
9
+ extend T::Sig
10
+
11
+ sig { returns(T::Enumerable[Dsl::Base]) }
12
+ attr_reader :generators
13
+
14
+ sig { returns(T::Array[Module]) }
15
+ attr_reader :requested_constants
16
+
17
+ sig { returns(T.proc.params(error: String).void) }
18
+ attr_reader :error_handler
19
+
20
+ sig do
21
+ params(
22
+ requested_constants: T::Array[Module],
23
+ requested_generators: T::Array[String],
24
+ error_handler: T.nilable(T.proc.params(error: String).void)
25
+ ).void
26
+ end
27
+ def initialize(requested_constants:, requested_generators: [], error_handler: nil)
28
+ @generators = T.let(
29
+ gather_generators(requested_generators),
30
+ T::Enumerable[Dsl::Base]
31
+ )
32
+ @requested_constants = requested_constants
33
+ @error_handler = error_handler || $stderr.method(:puts)
34
+ end
35
+
36
+ sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
37
+ def run(&blk)
38
+ constants_to_process = gather_constants(requested_constants)
39
+
40
+ if constants_to_process.empty?
41
+ report_error(<<~ERROR)
42
+ No classes/modules can be matched for RBI generation.
43
+ Please check that the requested classes/modules include processable DSL methods.
44
+ ERROR
45
+ end
46
+
47
+ constants_to_process.sort_by { |c| c.name.to_s }.each do |constant|
48
+ rbi = rbi_for_constant(constant)
49
+ next if rbi.nil?
50
+
51
+ blk.call(constant, rbi)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ sig { params(requested_generators: T::Array[String]).returns(Proc) }
58
+ def generator_filter(requested_generators)
59
+ return proc { true } if requested_generators.empty?
60
+
61
+ generators = requested_generators.map(&:downcase)
62
+
63
+ proc do |klass|
64
+ generator = klass.name&.sub(/^Tapioca::Compilers::Dsl::/, '')&.downcase
65
+ generators.include?(generator)
66
+ end
67
+ end
68
+
69
+ sig { params(requested_generators: T::Array[String]).returns(T::Enumerable[Dsl::Base]) }
70
+ def gather_generators(requested_generators)
71
+ generator_filter = generator_filter(requested_generators)
72
+
73
+ Dsl::Base.descendants.select(&generator_filter).map(&:new)
74
+ end
75
+
76
+ sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
77
+ def gather_constants(requested_constants)
78
+ constants = generators.map(&:processable_constants).reduce(Set.new, :union)
79
+ constants &= requested_constants unless requested_constants.empty?
80
+ constants
81
+ end
82
+
83
+ sig { params(constant: Module).returns(T.nilable(String)) }
84
+ def rbi_for_constant(constant)
85
+ parlour = Parlour::RbiGenerator.new(sort_namespaces: true)
86
+
87
+ generators.each do |generator|
88
+ next unless generator.handles?(constant)
89
+ generator.decorate(parlour.root, constant)
90
+ end
91
+
92
+ return if parlour.root.children.empty?
93
+
94
+ resolve_conflicts(parlour)
95
+
96
+ parlour.rbi("true").strip
97
+ end
98
+
99
+ sig { params(parlour: Parlour::RbiGenerator).void }
100
+ def resolve_conflicts(parlour)
101
+ Parlour::ConflictResolver.new.resolve_conflicts(parlour.root) do |msg, candidates|
102
+ error = StringIO.new
103
+ error.puts "=== Error ==="
104
+ error.puts msg
105
+ error.puts "# Candidates"
106
+ candidates.each_with_index do |candidate, index|
107
+ error.puts " #{index}. #{candidate.describe}"
108
+ end
109
+ report_error(error.string)
110
+ end
111
+ end
112
+
113
+ sig { params(error: String).returns(T.noreturn) }
114
+ def report_error(error)
115
+ handler = error_handler
116
+ handler.call(error)
117
+ exit(1)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require 'spoom'
5
+
6
+ module Tapioca
7
+ module Compilers
8
+ class RequiresCompiler
9
+ extend T::Sig
10
+
11
+ sig { params(sorbet_path: String).void }
12
+ def initialize(sorbet_path)
13
+ @sorbet_path = sorbet_path
14
+ end
15
+
16
+ sig { returns(String) }
17
+ def compile
18
+ config = Spoom::Sorbet::Config.parse_file(@sorbet_path)
19
+ files = collect_files(config)
20
+ files.flat_map do |file|
21
+ collect_requires(file).reject do |req|
22
+ name_in_project?(files, req)
23
+ end
24
+ end.sort.uniq.map do |name|
25
+ "require '#{name}'\n"
26
+ end.join
27
+ end
28
+
29
+ private
30
+
31
+ sig { params(config: Spoom::Sorbet::Config).returns(T::Array[String]) }
32
+ def collect_files(config)
33
+ config.paths.flat_map do |path|
34
+ path = (Pathname.new(@sorbet_path) / "../.." / path).cleanpath
35
+ if path.directory?
36
+ Dir.glob("#{path}/**/*.rb", File::FNM_EXTGLOB).reject do |file|
37
+ file_ignored_by_sorbet?(config, file)
38
+ end
39
+ else
40
+ [path.to_s]
41
+ end
42
+ end.sort.uniq
43
+ end
44
+
45
+ sig { params(file_path: String).returns(T::Enumerable[String]) }
46
+ def collect_requires(file_path)
47
+ File.read(file_path).lines.map do |line|
48
+ /^\s*require\s*(\(\s*)?['"](?<name>[^'"]+)['"](\s*\))?/.match(line) { |m| m["name"] }
49
+ end.compact
50
+ end
51
+
52
+ sig { params(config: Spoom::Sorbet::Config, file: String).returns(T::Boolean) }
53
+ def file_ignored_by_sorbet?(config, file)
54
+ config.ignore.any? do |path|
55
+ Regexp.new(Regexp.escape(path)) =~ file
56
+ end
57
+ end
58
+
59
+ sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
60
+ def name_in_project?(files, name)
61
+ files.any? do |file|
62
+ File.basename(file, '.rb') == name
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'pathname'
5
+ require 'shellwords'
6
+
7
+ module Tapioca
8
+ module Compilers
9
+ module Sorbet
10
+ SORBET = Pathname.new(Gem::Specification.find_by_name("sorbet-static").full_gem_path) / "libexec" / "sorbet"
11
+
12
+ class << self
13
+ extend(T::Sig)
14
+
15
+ sig { params(args: String).returns(String) }
16
+ def run(*args)
17
+ IO.popen(
18
+ [
19
+ sorbet_path,
20
+ "--quiet",
21
+ *args,
22
+ ].join(' '),
23
+ err: "/dev/null"
24
+ ).read
25
+ end
26
+
27
+ sig { returns(String) }
28
+ def sorbet_path
29
+ SORBET.to_s.shellescape
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -29,7 +29,7 @@ module Tapioca
29
29
  def generate
30
30
  symbols
31
31
  .sort
32
- .map(&method(:generate_from_symbol))
32
+ .map { |symbol| generate_from_symbol(symbol) }
33
33
  .compact
34
34
  .join("\n\n")
35
35
  .concat("\n")
@@ -176,13 +176,43 @@ module Tapioca
176
176
  return if symbol_ignored?(name) && methods.nil?
177
177
 
178
178
  [
179
+ compile_module_helpers(constant),
179
180
  compile_mixins(constant),
180
181
  compile_mixes_in_class_methods(constant),
182
+ compile_props(constant),
181
183
  methods,
182
184
  ].select { |b| b != "" }.join("\n\n")
183
185
  end
184
186
  end
185
187
 
188
+ sig { params(constant: Module).returns(String) }
189
+ def compile_module_helpers(constant)
190
+ abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
191
+
192
+ if abstract_type
193
+ indented("#{abstract_type}!")
194
+ elsif T::Private::Final.final_module?(constant)
195
+ indented("final!")
196
+ elsif T::Private::Sealed.sealed_module?(constant)
197
+ indented("sealed!")
198
+ else
199
+ ""
200
+ end
201
+ end
202
+
203
+ sig { params(constant: Module).returns(String) }
204
+ def compile_props(constant)
205
+ return "" unless T::Props::ClassMethods === constant
206
+
207
+ constant.props.map do |name, prop|
208
+ method = "prop"
209
+ method = "const" if prop.fetch(:immutable, false)
210
+ type = prop.fetch(:type_object, "T.untyped")
211
+
212
+ indented("#{method} :#{name}, #{type}")
213
+ end.join("\n")
214
+ end
215
+
186
216
  sig { params(name: String, constant: Module).returns(T.nilable(String)) }
187
217
  def compile_subconstants(name, constant)
188
218
  output = constants_of(constant).sort.uniq.map do |constant_name|
@@ -287,6 +317,7 @@ module Tapioca
287
317
  end
288
318
 
289
319
  prepends = prepend
320
+ .reverse
290
321
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
291
322
  .select(&method(:public_module?))
292
323
  .map do |mod|
@@ -297,6 +328,7 @@ module Tapioca
297
328
  end
298
329
 
299
330
  includes = include
331
+ .reverse
300
332
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
301
333
  .select(&method(:public_module?))
302
334
  .map do |mod|
@@ -304,6 +336,7 @@ module Tapioca
304
336
  end
305
337
 
306
338
  extends = extend
339
+ .reverse
307
340
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
308
341
  .select(&method(:public_module?))
309
342
  .map do |mod|
@@ -376,7 +409,7 @@ module Tapioca
376
409
  )
377
410
 
378
411
  instance_methods = compile_directly_owned_methods(name, constant)
379
- singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant), [:public])
412
+ singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
380
413
 
381
414
  return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
382
415
 
@@ -389,24 +422,44 @@ module Tapioca
389
422
 
390
423
  sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
391
424
  def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
392
- method_names_by_visibility(mod)
393
- .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
394
- .flat_map do |visibility, method_list|
395
- compiled = method_list.sort!.map do |name|
396
- next if name == :initialize
397
- compile_method(module_name, mod, mod.instance_method(name))
398
- end
399
- compiled.compact!
425
+ indent_step = 0
426
+ preamble = nil
427
+ postamble = nil
428
+
429
+ if mod.singleton_class?
430
+ indent_step = 1
431
+ preamble = indented("class << self")
432
+ postamble = indented("end")
433
+ end
400
434
 
401
- unless compiled.empty? || visibility == :public
402
- # add visibility badge
403
- compiled.unshift('', indented(visibility.to_s), '')
435
+ methods = with_indentation(indent_step) do
436
+ method_names_by_visibility(mod)
437
+ .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
438
+ .flat_map do |visibility, method_list|
439
+ compiled = method_list.sort!.map do |name|
440
+ next if name == :initialize
441
+ compile_method(module_name, mod, mod.instance_method(name))
442
+ end
443
+ compiled.compact!
444
+
445
+ unless compiled.empty? || visibility == :public
446
+ # add visibility badge
447
+ compiled.unshift('', indented(visibility.to_s), '')
448
+ end
449
+
450
+ compiled
404
451
  end
452
+ .compact
453
+ .join("\n")
454
+ end
405
455
 
406
- compiled
407
- end
408
- .compact
409
- .join("\n")
456
+ return "" if methods.strip == ""
457
+
458
+ [
459
+ preamble,
460
+ methods,
461
+ postamble,
462
+ ].compact.join("\n")
410
463
  end
411
464
 
412
465
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -418,6 +471,16 @@ module Tapioca
418
471
  }
419
472
  end
420
473
 
474
+ sig { params(constant: Module, method_name: String).returns(T::Boolean) }
475
+ def struct_method?(constant, method_name)
476
+ return false unless T::Props::ClassMethods === constant
477
+
478
+ constant
479
+ .props
480
+ .keys
481
+ .include?(method_name.gsub(/=$/, '').to_sym)
482
+ end
483
+
421
484
  sig do
422
485
  params(
423
486
  symbol_name: String,
@@ -430,12 +493,37 @@ module Tapioca
430
493
  return unless method.owner == constant
431
494
  return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
432
495
 
496
+ signature = signature_of(method)
497
+ method = signature.method if signature
498
+
433
499
  method_name = method.name.to_s
434
500
  return unless valid_method_name?(method_name)
501
+ return if struct_method?(constant, method_name)
502
+ return if method_name.start_with?("__t_props_generated_")
435
503
 
436
504
  params = T.let(method.parameters, T::Array[T::Array[Symbol]])
437
505
  parameters = params.map do |(type, name)|
438
- name ||= :_
506
+ unless name
507
+ # For attr_writer methods, Sorbet signatures have the name
508
+ # of the method (without the trailing = sign) as the name of
509
+ # the only parameter. So, if the parameter does not have a name
510
+ # then the replacement name should be the name of the method
511
+ # (minus trailing =) if and only if there is a signature for the
512
+ # method and the parameter is required and there is a single
513
+ # parameter and the signature also defines a single parameter and
514
+ # the name of the method ends with a = character.
515
+ writer_method_with_sig = signature &&
516
+ type == :req &&
517
+ params.size == 1 &&
518
+ signature.arg_types.size == 1 &&
519
+ method_name[-1] == "="
520
+
521
+ name = if writer_method_with_sig
522
+ method_name[0...-1].to_sym
523
+ else
524
+ :_
525
+ end
526
+ end
439
527
 
440
528
  # Sanitize param names
441
529
  name = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
@@ -444,13 +532,13 @@ module Tapioca
444
532
  when :req
445
533
  name
446
534
  when :opt
447
- "#{name} = _"
535
+ "#{name} = T.unsafe(nil)"
448
536
  when :rest
449
537
  "*#{name}"
450
538
  when :keyreq
451
539
  "#{name}:"
452
540
  when :key
453
- "#{name}: _"
541
+ "#{name}: T.unsafe(nil)"
454
542
  when :keyrest
455
543
  "**#{name}"
456
544
  when :block
@@ -458,10 +546,54 @@ module Tapioca
458
546
  end
459
547
  end.join(', ')
460
548
 
461
- method_name = "#{'self.' if constant.singleton_class?}#{method_name}"
462
549
  parameters = "(#{parameters})" if parameters != ""
463
550
 
464
- indented("def #{method_name}#{parameters}; end")
551
+ signature_str = indented(compile_signature(signature)) if signature
552
+ [
553
+ signature_str,
554
+ indented("def #{method_name}#{parameters}; end"),
555
+ ].compact.join("\n")
556
+ end
557
+
558
+ TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
559
+
560
+ sig { params(signature: T.untyped).returns(String) }
561
+ def compile_signature(signature)
562
+ params = signature.arg_types
563
+ params += signature.kwarg_types.to_a
564
+ params << [signature.rest_name, signature.rest_type] if signature.has_rest
565
+ params << [signature.block_name, signature.block_type] if signature.block_name
566
+
567
+ params = params.compact.map { |name, type| "#{name}: #{type}" }.join(", ")
568
+ returns = type_of(signature.return_type)
569
+
570
+ type_parameters = (params + returns).scan(TYPE_PARAMETER_MATCHER).flatten.uniq.map { |p| ":#{p}" }.join(", ")
571
+ type_parameters = ".type_parameters(#{type_parameters})" unless type_parameters.empty?
572
+
573
+ mode = case signature.mode
574
+ when "abstract"
575
+ ".abstract"
576
+ when "override"
577
+ ".override"
578
+ when "overridable_override"
579
+ ".overridable.override"
580
+ when "overridable"
581
+ ".overridable"
582
+ else
583
+ ""
584
+ end
585
+
586
+ signature_body = +""
587
+ signature_body << mode
588
+ signature_body << type_parameters
589
+ signature_body << ".params(#{params})" unless params.empty?
590
+ signature_body << ".returns(#{returns})"
591
+ signature_body = signature_body
592
+ .gsub(".returns(<VOID>)", ".void")
593
+ .gsub("<NOT-TYPED>", "T.untyped")
594
+ .gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
595
+
596
+ "sig { #{signature_body} }"
465
597
  end
466
598
 
467
599
  sig { params(symbol_name: String).returns(T::Boolean) }
@@ -480,16 +612,17 @@ module Tapioca
480
612
  sig do
481
613
  type_parameters(:U)
482
614
  .params(
615
+ step: Integer,
483
616
  _blk: T.proc
484
617
  .returns(T.type_parameter(:U))
485
618
  )
486
619
  .returns(T.type_parameter(:U))
487
620
  end
488
- def with_indentation(&_blk)
489
- @indent += 2
621
+ def with_indentation(step = 1, &_blk)
622
+ @indent += 2 * step
490
623
  yield
491
624
  ensure
492
- @indent -= 2
625
+ @indent -= 2 * step
493
626
  end
494
627
 
495
628
  sig { params(str: String).returns(String) }
@@ -624,7 +757,7 @@ module Tapioca
624
757
  return nil
625
758
  end
626
759
 
627
- name_of(target)
760
+ raw_name_of(target)
628
761
  end
629
762
 
630
763
  sig { params(constant: Module).returns(T.nilable(String)) }
@@ -644,6 +777,18 @@ module Tapioca
644
777
  Class.instance_method(:superclass).bind(constant).call
645
778
  end
646
779
 
780
+ sig { params(method: T.any(UnboundMethod, Method)).returns(T.untyped) }
781
+ def signature_of(method)
782
+ T::Private::Methods.signature_for_method(method)
783
+ rescue LoadError, StandardError
784
+ nil
785
+ end
786
+
787
+ sig { params(constant: Module).returns(String) }
788
+ def type_of(constant)
789
+ constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
790
+ end
791
+
647
792
  sig { params(constant: Module, other: BasicObject).returns(T::Boolean).checked(:never) }
648
793
  def are_equal?(constant, other)
649
794
  BasicObject.instance_method(:equal?).bind(constant).call(other)