Skip to content

Commit e1504c6

Browse files
committed
Support generic ActiveJob return types
1 parent 9153f30 commit e1504c6

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

lib/tapioca/dsl/compilers/active_job.rb

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def decorate
4747

4848
root.create_path(constant) do |job|
4949
method = constant.instance_method(:perform)
50-
constant_name = name_of(constant)
50+
constant_name = generic_name_of(constant) #: as !nil
5151
parameters = compile_method_parameters_to_rbi(method)
5252
return_type = compile_method_return_type_to_rbi(method)
5353

@@ -69,7 +69,27 @@ def decorate
6969

7070
private
7171

72-
#: (Array[RBI::TypedParam] parameters, String? constant_name) -> Array[RBI::TypedParam]
72+
# Resolves a constant name into a valid Sorbet type reference,
73+
# applying `T.untyped` for any unfixed generic type variables.
74+
#
75+
# @example
76+
# generic_name_of(StandardJob) # => "StandardJob"
77+
# generic_name_of(GenericJob) # => "GenericJob[T.untyped]"
78+
#: (Module[top] constant) -> String?
79+
def generic_name_of(constant)
80+
type_name = name_of(constant)
81+
return type_name if !type_name || type_name.end_with?("]")
82+
83+
type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant)
84+
return type_name unless type_variables
85+
86+
type_variables = type_variables.reject(&:fixed?)
87+
return type_name if type_variables.empty?
88+
89+
"#{type_name}[#{type_variables.map { "T.untyped" }.join(", ")}]"
90+
end
91+
92+
#: (Array[RBI::TypedParam] parameters, String constant_name) -> Array[RBI::TypedParam]
7393
def perform_later_parameters(parameters, constant_name)
7494
if ::Gem::Requirement.new(">= 7.0").satisfied_by?(::ActiveJob.gem_version)
7595
parameters.reject! { |typed_param| RBI::BlockParam === typed_param.param }

spec/tapioca/dsl/compilers/active_job_spec.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,69 @@ def perform_now(user_id); end
111111
assert_equal(expected, rbi_for(:NotifyJob))
112112
end
113113

114+
it "generates correct RBI file for a generic job" do
115+
add_ruby_file("job.rb", <<~RUBY)
116+
class GenericJob < ActiveJob::Base
117+
extend T::Sig
118+
extend T::Generic
119+
120+
Elem = type_member
121+
122+
sig { params(value: Elem).returns(Elem) }
123+
def perform(value)
124+
value
125+
end
126+
end
127+
RUBY
128+
129+
expected = template(<<~RBI)
130+
# typed: strong
131+
132+
class GenericJob
133+
class << self
134+
sig { params(value: Elem, block: T.nilable(T.proc.params(job: GenericJob[T.untyped]).void)).returns(T.any(GenericJob[T.untyped], FalseClass)) }
135+
def perform_later(value, &block); end
136+
137+
sig { params(value: Elem).returns(Elem) }
138+
def perform_now(value); end
139+
end
140+
end
141+
RBI
142+
assert_equal(expected, rbi_for(:GenericJob))
143+
end
144+
145+
it "generates correct RBI file for a job with multiple type members" do
146+
add_ruby_file("job.rb", <<~RUBY)
147+
class GenericJob < ActiveJob::Base
148+
extend T::Sig
149+
extend T::Generic
150+
151+
Input = type_member
152+
Output = type_member
153+
154+
sig { params(value: Input).returns(Output) }
155+
def perform(value)
156+
raise NotImplementedError
157+
end
158+
end
159+
RUBY
160+
161+
expected = template(<<~RBI)
162+
# typed: strong
163+
164+
class GenericJob
165+
class << self
166+
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)) }
167+
def perform_later(value, &block); end
168+
169+
sig { params(value: Input).returns(Output) }
170+
def perform_now(value); end
171+
end
172+
end
173+
RBI
174+
assert_equal(expected, rbi_for(:GenericJob))
175+
end
176+
114177
it "generates correct RBI file for subclass with block argument" do
115178
add_ruby_file("job.rb", <<~RUBY)
116179
class NotifyJob < ActiveJob::Base

0 commit comments

Comments
 (0)