Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tapioca compiler #533

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ gem "csv" # required for Ruby 3.4+

# for unit testing optional sorbet support
gem "sorbet-runtime"
gem "tapioca"

gem "logger"
33 changes: 33 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ GEM
tzinfo (~> 2.0)
ast (2.4.2)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
coderay (1.1.3)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
csv (3.3.2)
drb (2.2.1)
erubi (1.13.1)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.6)
Expand All @@ -55,17 +57,22 @@ GEM
mono_logger (1.1.2)
multi_json (1.15.0)
mutex_m (0.2.0)
netrc (0.11.0)
parallel (1.25.1)
parser (3.3.3.0)
ast (~> 2.4.1)
racc
prism (1.3.0)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.8.0)
rack (3.1.8)
rainbow (3.1.1)
rake (13.2.1)
rbi (0.2.3)
prism (~> 1.0)
sorbet-runtime (>= 0.5.9204)
redis (5.3.0)
redis-client (>= 0.22.0)
redis-client (0.23.0)
Expand Down Expand Up @@ -103,12 +110,37 @@ GEM
redis-client (>= 0.19.0)
sinatra (1.0)
rack (>= 1.0)
sorbet (0.5.11460)
sorbet-static (= 0.5.11460)
sorbet-runtime (0.5.11460)
sorbet-static (0.5.11460-universal-darwin)
sorbet-static-and-runtime (0.5.11460)
sorbet (= 0.5.11460)
sorbet-runtime (= 0.5.11460)
spoom (1.5.0)
erubi (>= 1.10.0)
prism (>= 0.28.0)
sorbet-static-and-runtime (>= 0.5.10187)
thor (>= 0.19.2)
tapioca (0.16.7)
benchmark
bundler (>= 2.2.25)
netrc (>= 0.11.0)
parallel (>= 1.21.0)
rbi (~> 0.2)
sorbet-static-and-runtime (>= 0.5.11087)
spoom (>= 1.2.0)
thor (>= 1.2.0)
yard-sorbet
thor (1.3.2)
timeout (0.4.1)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
yard (0.9.37)
yard-sorbet (0.9.0)
sorbet-runtime
yard

PLATFORMS
ruby
Expand All @@ -129,6 +161,7 @@ DEPENDENCIES
rubocop-shopify
sidekiq
sorbet-runtime
tapioca
yard

BUNDLED WITH
Expand Down
120 changes: 120 additions & 0 deletions lib/tapioca/dsl/compilers/job_iteration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# typed: strict
# frozen_string_literal: true

return unless defined?(JobIteration::Iteration)

module Tapioca
module Dsl
module Compilers
class JobIteration < Compiler
extend T::Sig

ConstantType = type_member { { fixed: T.class_of(::JobIteration::Iteration) } }

sig { override.void }
def decorate
return unless constant.instance_methods(false).include?(:build_enumerator)

root.create_path(constant) do |job|
method = constant.instance_method(:build_enumerator)
constant_name = name_of(constant)
expanded_parameters = compile_method_parameters_to_rbi(method).reject do |typed_param|
typed_param.param.name == "cursor"
end

job.create_method(
"perform_later",
parameters: perform_later_parameters(expanded_parameters, constant_name),
return_type: "T.any(#{constant_name}, FalseClass)",
class_method: true,
)

job.create_method(
"perform_now",
parameters: expanded_parameters,
return_type: "void",
class_method: true,
)

job.create_method(
"perform",
parameters: expanded_parameters,
return_type: "void",
class_method: false,
)
end
end

private

sig do
params(
parameters: T::Array[RBI::TypedParam],
constant_name: T.nilable(String),
).returns(T::Array[RBI::TypedParam])
end
def perform_later_parameters(parameters, constant_name)
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)",
)]
else
parameters
end
end

def compile_method_parameters_to_rbi(method_def)
Copy link
Member

Choose a reason for hiding this comment

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

Do you need to copy this complex method over? I would have instead done a call to compile_method_parameters_to_rbi and then walked over the result to find all required params of type T::Types:FixedHash and replace them with the keyword parameters instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

I want to do that too but AFAICT the T::Types information is not captured in the rbi types:

<RBI::TypedParam param=#<RBI::ReqParam:0x000000013295e420 @comments=[], @loc=nil, @name="params", @parent_tree=nil> type="{shop_id: ::Integer, resource_types: T::Array[::ResourceType], locale: ::Locale, metadata: T.nilable(::String)}">

Copy link
Member

Choose a reason for hiding this comment

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

Right, but you can always match that data with method_types = parameters_types_from_signature(method_def, signature) to find all required params of that type.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry, those are also strings. I guess you could do:

signature = signature_of(method_def)
fixed_hash_args = signature.arg_types.select { |arg_type| arg_type[1].is_?(T::Types::FixedHash) }

and loop over those to match the params to the ones you need to replace.

signature = signature_of(method_def)
method_def = signature.nil? ? method_def : signature.method
method_types = parameters_types_from_signature(method_def, signature)

parameters = T.let(method_def.parameters, T::Array[[Symbol, T.nilable(Symbol)]])

parameters.each_with_index.flat_map do |(type, name), index|
fallback_arg_name = "_arg#{index}"

name = name ? name.to_s : fallback_arg_name
name = fallback_arg_name unless valid_parameter_name?(name)
method_type = T.must(method_types[index])

case type
when :req
if signature && (type_value = signature.arg_types[index][1]) && type_value.is_a?(T::Types::FixedHash)
type_value.types.map do |key, value|
create_kw_param(key.to_s, type: value.to_s)
end
else
create_param(name, type: method_type)
end
when :opt
create_opt_param(name, type: method_type, default: "T.unsafe(nil)")
when :rest
create_rest_param(name, type: method_type)
when :keyreq
create_kw_param(name, type: method_type)
when :key
create_kw_opt_param(name, type: method_type, default: "T.unsafe(nil)")
when :keyrest
create_kw_rest_param(name, type: method_type)
when :block
create_block_param(name, type: method_type)
else
raise "Unknown type `#{type}`."
end
end
end

class << self
extend T::Sig

sig { override.returns(T::Enumerable[Module]) }
def gather_constants
all_classes.select { |c| ::JobIteration::Iteration > c }
end
end
end
end
end
end
Loading
Loading