Tapioca::Dsl::Compilers::ActiveJob missing Exception as a possible return type in perform_now type compiled signature
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 873
- Forks
- 164
- Avg merge
- 4d 27m
- Merged PRs (30d)
- 9
Description
Description
Tapioca's compiled type signature for ActiveJob::Base.perform_now essentially uses the same as ActiveJob::Base#perform. However, this is not fully representative of the behaviour because .perform_now will return the exception that got raised inside #perform when that exception has been rescued with rescue_from or retried with retry_on. Other scenarios are possible, these are just the 2 scenarios I tested.
To illustrate this point, consider the following job definition:
# failing_job_with_rescue_from_noop.rb
# typed: strict
# frozen_string_literal: true
class FailingJobWithRescueFromNoop < ActiveJob::Base CustomCops/EssentialsApplicationJob,Rails/ApplicationJob
rescue_from(StandardError) { p "error!" }
sig { void }
def perform
raise "FailingJobWithRescueFromNoop"
end
end
Actual behaviour
Running bin/tapioca dsl FailingJobWithRescueFromNoop produces the following RBI. Note the type signature for .perform_now:
# failing_job_with_rescue_from_noop.rbi
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `FailingJobWithRescueFromNoop`.
# Please instead update this file by running `bin/tapioca dsl FailingJobWithRescueFromNoop`.
class FailingJobWithRescueFromNoop
class << self
sig { params(block: T.nilable(T.proc.params(job: FailingJobWithRescueFromNoop).void)).returns(T.any(FailingJobWithRescueFromNoop, FalseClass)) }
def perform_later(&block); end
sig { void }
def perform_now; end
end
end
However, when one runs the following in a rails console:
> result = FailingJobWithRescueFromNoop.perform_now rescue "error raised"
> result
=> #<RuntimeError: FailingJob>
As illustrated above, .perform_now actually returns the error, it does not raise it. This appears to be expected behaviour as confirmed in a comment in rails/rails#48281.
Shopify/job-iteration also had to update their custom Tapioca compiler with Shopify/job-iteration#537.
Expected behaviour
Assuming we want the RBI to reflect the possibility of returning exceptions, running bin/tapioca dsl FailingJobWithRescueFromNoop should instead produce something like the following for perform_now:
# failing_job_with_rescue_from_noop.rbi
# ...
class FailingJobWithRescueFromNoop
class << self
# ...
sig { returns(T.any(NilClass, Exception)) }
def perform_now; end
end
end
This is what was done in Shopify/job-iteration#537, but this is only possible because that gem provides a #perform method that always returns nil. If used in the general case, any type sigs for #perform that currently have "void" as the return type will fail typechecks if the method returns a non-nil value. Perhaps using T.untyped is the next best thing here?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at Tapioca::Dsl::Compilers::ActiveJob and reproduce the issue with bin/tapioca dsl FailingJobWithRescueNoop. Compare the generated perform_now signature with the described ActiveJob behavior and consider how it interacts with existing perform signatures, including void. Done means the generated RBI represents the possible exception return without causing incompatible type checks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rails, ruby
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100