diff --git a/lib/tapioca/dsl/compilers/active_job.rb b/lib/tapioca/dsl/compilers/active_job.rb index 6698f1f21..8be9dc326 100644 --- a/lib/tapioca/dsl/compilers/active_job.rb +++ b/lib/tapioca/dsl/compilers/active_job.rb @@ -47,14 +47,15 @@ def decorate root.create_path(constant) do |job| method = constant.instance_method(:perform) - constant_name = name_of(constant) + constant_name = T.must(name_of(constant)) + job_type = generic_job_type(constant_name) parameters = compile_method_parameters_to_rbi(method) return_type = compile_method_return_type_to_rbi(method) job.create_method( "perform_later", - parameters: perform_later_parameters(parameters, constant_name), - return_type: "T.any(#{constant_name}, FalseClass)", + parameters: perform_later_parameters(parameters, job_type), + return_type: "T.any(#{job_type}, FalseClass)", class_method: true, ) @@ -69,13 +70,26 @@ def decorate private - #: (Array[RBI::TypedParam] parameters, String? constant_name) -> Array[RBI::TypedParam] - def perform_later_parameters(parameters, constant_name) + #: (String constant_name) -> String + def generic_job_type(constant_name) + return constant_name unless T::Generic === constant + + type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant) + return constant_name unless type_variables + + type_arguments = type_variables.reject(&:fixed?).map { "T.untyped" } + return constant_name if type_arguments.empty? + + "#{constant_name}[#{type_arguments.join(", ")}]" + end + + #: (Array[RBI::TypedParam] parameters, String job_type) -> Array[RBI::TypedParam] + def perform_later_parameters(parameters, job_type) if ::Gem::Requirement.new(">= 7.0").satisfied_by?(::ActiveJob.gem_version) parameters.reject! { |typed_param| RBI::BlockParam === typed_param.param } parameters + [create_block_param( "block", - type: "T.nilable(T.proc.params(job: #{constant_name}).void)", + type: "T.nilable(T.proc.params(job: #{job_type}).void)", )] else parameters diff --git a/spec/tapioca/dsl/compilers/active_job_spec.rb b/spec/tapioca/dsl/compilers/active_job_spec.rb index aa47ab272..7ba2f4922 100644 --- a/spec/tapioca/dsl/compilers/active_job_spec.rb +++ b/spec/tapioca/dsl/compilers/active_job_spec.rb @@ -111,6 +111,69 @@ def perform_now(user_id); end assert_equal(expected, rbi_for(:NotifyJob)) end + it "generates correct RBI file for a generic job" do + add_ruby_file("job.rb", <<~RUBY) + class GenericJob < ActiveJob::Base + extend T::Sig + extend T::Generic + + Elem = type_member + + sig { params(value: Elem).returns(Elem) } + def perform(value) + value + end + end + RUBY + + expected = template(<<~RBI) + # typed: strong + + class GenericJob + class << self + sig { params(value: Elem, block: T.nilable(T.proc.params(job: GenericJob[T.untyped]).void)).returns(T.any(GenericJob[T.untyped], FalseClass)) } + def perform_later(value, &block); end + + sig { params(value: Elem).returns(Elem) } + def perform_now(value); end + end + end + RBI + assert_equal(expected, rbi_for(:GenericJob)) + end + + it "generates correct RBI file for a job with multiple type members" do + add_ruby_file("job.rb", <<~RUBY) + class GenericJob < ActiveJob::Base + extend T::Sig + extend T::Generic + + Input = type_member + Output = type_member + + sig { params(value: Input).returns(Output) } + def perform(value) + raise NotImplementedError + end + end + RUBY + + expected = template(<<~RBI) + # typed: strong + + class GenericJob + class << self + sig { params(value: Input, block: T.nilable(T.proc.params(job: GenericJob[T.untyped, T.untyped]).void)).returns(T.any(GenericJob[T.untyped, T.untyped], FalseClass)) } + def perform_later(value, &block); end + + sig { params(value: Input).returns(Output) } + def perform_now(value); end + end + end + RBI + assert_equal(expected, rbi_for(:GenericJob)) + end + it "generates correct RBI file for subclass with block argument" do add_ruby_file("job.rb", <<~RUBY) class NotifyJob < ActiveJob::Base