tapioca 0.3.1 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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)