Skip to content

Tapioca::Dsl::Compilers::ActiveJob missing Exception as a possible return type in perform_now type compiled signature #2353

@JiaboHou

Description

@JiaboHou

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions