Skip to content

Fix generic ActiveJob signatures - #2696

Open
iamhaseebn wants to merge 1 commit into
Shopify:mainfrom
iamhaseebn:fix-active-job-generic-return-types
Open

Fix generic ActiveJob signatures#2696
iamhaseebn wants to merge 1 commit into
Shopify:mainfrom
iamhaseebn:fix-active-job-generic-return-types

Conversation

@iamhaseebn

@iamhaseebn iamhaseebn commented Aug 8, 2026

Copy link
Copy Markdown

Motivation

Generic ActiveJob subclasses currently generate bare job class references in perform_later signatures. Sorbet requires generic classes to include type arguments, so the generated RBI fails type checking.

Fixes #2233.

Implementation

  • Detect generic ActiveJob subclasses using T::Generic === constant.
  • Look up their non-fixed type members and use T.untyped for each generated type argument.
  • Use the parameterized job type for both the callback parameter and the perform_later return type.
  • Preserve existing signatures for non-generic jobs.

Tests

  • Added regression coverage for jobs with one type member and multiple type members.
  • Ran bundle exec bin/test spec/tapioca/dsl/compilers/active_job_spec.rb.
  • Ran the complete suite: 821 tests, 3,190 assertions, 0 failures, 0 errors, 2 skips.
  • Ran bundle exec bin/typecheck and bundle exec bin/style.
  • Ran documentation, README, gem RBI, and shim verification checks.

@iamhaseebn
iamhaseebn requested a review from a team as a code owner August 8, 2026 20:46
@iamhaseebn

Copy link
Copy Markdown
Author

I have signed the CLA!

Comment thread lib/tapioca/dsl/compilers/active_job.rb Outdated
Comment on lines +73 to +74
#: (String constant_name) -> String
def generic_job_type(constant_name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's document this

Suggested change
#: (String constant_name) -> String
def generic_job_type(constant_name)
# Resolves the constant name into a valid Sorbet type reference,
# applying `T.untyped` for any unfixed generic type variables.
#
# @example
# generic_job_type("StandardJob") # => "StandardJob"
# generic_job_type("GenericJob") # => "GenericJob[T.untyped]"
#: (String constant_name) -> String
def generic_job_type(constant_name)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I've added documentation for generic_job_type in 85766fa, including examples for standard and generic jobs. The focused ActiveJob spec, RuboCop, docs generation, and typecheck all pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @iamhaseebn, that doc got lost in one of your subsequent commits.

Can you add it back, and tidy up the git history? I think most (all?) of this makes sense to squash down to just one commit.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks - I've restored the documentation and squashed this down to one signed commit.

Comment thread lib/tapioca/dsl/compilers/active_job.rb Outdated
Comment thread lib/tapioca/dsl/compilers/active_job.rb Outdated
@iamhaseebn
iamhaseebn force-pushed the fix-active-job-generic-return-types branch from 77897fd to e1504c6 Compare August 13, 2026 00:20

@paracycle paracycle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realize type_name_of is a better name for the method, since it returns the proper Sorbet type name, instead of the constant name. Also, it is much safer to make it call qualified_name_of so that the names are anchored properly.

I think after this, I am good to merge. Thank you!

Comment thread lib/tapioca/dsl/compilers/active_job.rb Outdated
private

#: (Array[RBI::TypedParam] parameters, String? constant_name) -> Array[RBI::TypedParam]
# Resolves a constant name into a valid Sorbet type reference,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Resolves a constant name into a valid Sorbet type reference,
# Resolves a constant into a valid Sorbet type reference,

Comment thread lib/tapioca/dsl/compilers/active_job.rb Outdated
Comment on lines +76 to +79
# generic_name_of(StandardJob) # => "StandardJob"
# generic_name_of(GenericJob) # => "GenericJob[T.untyped]"
#: (Module[top] constant) -> String?
def generic_name_of(constant)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# generic_name_of(StandardJob) # => "StandardJob"
# generic_name_of(GenericJob) # => "GenericJob[T.untyped]"
#: (Module[top] constant) -> String?
def generic_name_of(constant)
# type_name_of(StandardJob) # => "::StandardJob"
# type_name_of(GenericJob) # => "::SomeModule::GenericJob[T.untyped]"
#: (Module[top] constant) -> String?
def type_name_of(constant)

Comment thread lib/tapioca/dsl/compilers/active_job.rb Outdated
# generic_name_of(GenericJob) # => "GenericJob[T.untyped]"
#: (Module[top] constant) -> String?
def generic_name_of(constant)
type_name = name_of(constant)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type_name = name_of(constant)
type_name = qualified_name_of(constant)

assert_equal(expected, rbi_for(:NotifyJob))
end

it "generates correct RBI file for a generic job" do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test adds much value given the test below exists. Can we just name the test below "generates correct RBI file for a generic job" instead and remove this?

Comment on lines +151 to +152
Input = type_member
Output = type_member

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing with multiple kinds of type members would be great:

Suggested change
Input = type_member
Output = type_member
Input = type_member
Fixed = type_member { { fixed: String } }
Output = type_member { { upper: ActiveRecord::Base } }

@iamhaseebn
iamhaseebn force-pushed the fix-active-job-generic-return-types branch from e1504c6 to 4b9ca6e Compare August 13, 2026 15:50
@iamhaseebn

Copy link
Copy Markdown
Author

Thanks! I've applied all five suggestions and pushed the update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tapioca::Dsl::Compilers::ActiveJob creates an invalid RBI for generic job classes

3 participants