Skip to content
Open
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
11 changes: 8 additions & 3 deletions lib/spoom/sorbet/translate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ def sorbet_sigs_to_rbs_comments(

# Converts all the RBS comments in the given Ruby code to `sig` nodes.
# It also handles type members and class annotations.
#: (String ruby_contents, file: String, ?max_line_length: Integer?) -> String
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil)
RBSCommentsToSorbetSigs.new(ruby_contents, file: file, max_line_length: max_line_length).rewrite
#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> String
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
RBSCommentsToSorbetSigs.new(
ruby_contents,
file: file,
max_line_length: max_line_length,
overloads_strategy: overloads_strategy,
).rewrite
end

# Converts all `T.let` and `T.cast` nodes to RBS comments in the given Ruby code.
Expand Down
40 changes: 36 additions & 4 deletions lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ module Translate
class RBSCommentsToSorbetSigs < Translator
include Spoom::RBS::ExtractRBSComments

#: (String, file: String, ?max_line_length: Integer?) -> void
def initialize(ruby_contents, file:, max_line_length: nil)
ALLOWED_OVERLOAD_STRATEGIES = [:translate_all, :translate_last, :raise].freeze #: Array[Symbol]

#: (String, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> void
def initialize(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
super(ruby_contents, file: file)

unless ALLOWED_OVERLOAD_STRATEGIES.include?(overloads_strategy)
raise ArgumentError, "Unknown overloads_strategy: #{overloads_strategy.inspect}. " \
"Must be one of: #{ALLOWED_OVERLOAD_STRATEGIES.map(&:inspect).join(", ")}"
end

@max_line_length = max_line_length
@overloads_strategy = overloads_strategy
end

# @override
Expand Down Expand Up @@ -80,7 +88,9 @@ def visit_attr(node)

return if comments.signatures.empty?

comments.signatures.each do |signature|
signatures = apply_overloads_strategy(comments.signatures)

signatures.each do |signature|
attr_type = ::RBS::Parser.parse_type(signature.string)
sig = RBI::Sig.new

Expand Down Expand Up @@ -116,11 +126,13 @@ def rewrite_def(def_node, comments)
return if comments.empty?
return if comments.signatures.empty?

signatures = apply_overloads_strategy(comments.signatures)

builder = RBI::Parser::TreeBuilder.new(@ruby_contents, comments: [], file: @file)
builder.visit(def_node)
rbi_node = builder.tree.nodes.first #: as RBI::Method

comments.signatures.each do |signature|
signatures.each do |signature|
begin
method_type = ::RBS::Parser.parse_method_type(signature.string)
rescue ::RBS::ParsingError
Expand Down Expand Up @@ -153,6 +165,26 @@ def rewrite_def(def_node, comments)
end
end

#: (Array[RBS::Signature]) -> Array[RBS::Signature]
def apply_overloads_strategy(signatures)
return signatures if signatures.size <= 1

case @overloads_strategy
when :translate_all
signatures
when :translate_last
kept = signatures.first #: as RBS::Signature
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.

It is strange that we keep first in a branch that says ..._last. Do we get the signatures in reverse order? It might be good to add a note here to that effect.

signatures.drop(1).each do |signature|
from = adjust_to_line_start(signature.location.start_offset)
to = adjust_to_line_end(signature.location.end_offset)
@rewriter << Source::Delete.new(from, to)
end
[kept]
else # :raise
raise Error, "Method has multiple overloaded signatures"
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.

Can we give more context on which method, where? This could be hard to find.

end
end

#: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode) -> void
def apply_class_annotations(node)
comments = node_rbs_comments(node)
Expand Down
27 changes: 23 additions & 4 deletions rbi/spoom.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -2851,8 +2851,15 @@ Spoom::Sorbet::Sigils::VALID_STRICTNESS = T.let(T.unsafe(nil), Array)

module Spoom::Sorbet::Translate
class << self
sig { params(ruby_contents: ::String, file: ::String, max_line_length: T.nilable(::Integer)).returns(::String) }
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: T.unsafe(nil)); end
sig do
params(
ruby_contents: ::String,
file: ::String,
max_line_length: T.nilable(::Integer),
overloads_strategy: ::Symbol
).returns(::String)
end
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: T.unsafe(nil), overloads_strategy: T.unsafe(nil)); end

sig do
params(
Expand Down Expand Up @@ -2890,8 +2897,15 @@ class Spoom::Sorbet::Translate::Error < ::Spoom::Error; end
class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Translate::Translator
include ::Spoom::RBS::ExtractRBSComments

sig { params(ruby_contents: ::String, file: ::String, max_line_length: T.nilable(::Integer)).void }
def initialize(ruby_contents, file:, max_line_length: T.unsafe(nil)); end
sig do
params(
ruby_contents: ::String,
file: ::String,
max_line_length: T.nilable(::Integer),
overloads_strategy: ::Symbol
).void
end
def initialize(ruby_contents, file:, max_line_length: T.unsafe(nil), overloads_strategy: T.unsafe(nil)); end

sig { override.params(node: ::Prism::CallNode).void }
def visit_call_node(node); end
Expand Down Expand Up @@ -2927,6 +2941,9 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Trans
sig { params(annotations: T::Array[::Spoom::RBS::Annotation], sig: ::RBI::Sig).void }
def apply_member_annotations(annotations, sig); end

sig { params(signatures: T::Array[::Spoom::RBS::Signature]).returns(T::Array[::Spoom::RBS::Signature]) }
def apply_overloads_strategy(signatures); end

sig { params(comments: T::Array[::Prism::Comment]).void }
def apply_type_aliases(comments); end

Expand All @@ -2940,6 +2957,8 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Trans
def visit_attr(node); end
end

Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::ALLOWED_OVERLOAD_STRATEGIES = T.let(T.unsafe(nil), Array)

class Spoom::Sorbet::Translate::SorbetAssertionsToRBSComments < ::Spoom::Sorbet::Translate::Translator
sig do
params(
Expand Down
76 changes: 73 additions & 3 deletions test/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,81 @@ class Range
RB
end

def test_translate_overloads_translate_all_is_default
contents = <<~RB
class Foo
#: () { (Integer) -> void } -> void
#: () -> Enumerator[Integer, void]
def each(&block); end
end
RB

assert_equal(<<~RB, rbs_comments_to_sorbet_sigs(contents))
class Foo
sig { params(block: ::T.proc.params(arg0: Integer).void).void }
sig { returns(::T::Enumerator[Integer, void]) }
def each(&block); end
end
RB
end

def test_translate_overloads_translate_last
contents = <<~RB
class Foo
#: () { (Integer) -> void } -> void
#: () -> Enumerator[Integer, void]
def each(&block); end
end
RB

assert_equal(<<~RB, rbs_comments_to_sorbet_sigs(contents, overloads_strategy: :translate_last))
class Foo
sig { returns(::T::Enumerator[Integer, void]) }
def each(&block); end
end
RB
end

def test_translate_overloads_raise
contents = <<~RB
class Foo
#: () { (Integer) -> void } -> void
#: () -> Enumerator[Integer, void]
def each(&block); end
end
RB

assert_raises(Translate::Error) do
rbs_comments_to_sorbet_sigs(contents, overloads_strategy: :raise)
end
end

def test_translate_overloads_single_signature_unaffected
contents = <<~RB
class Foo
#: () -> void
def foo; end
end
RB

assert_equal(<<~RB, rbs_comments_to_sorbet_sigs(contents, overloads_strategy: :translate_last))
class Foo
sig { void }
def foo; end
end
RB
end

private

#: (String, ?max_line_length: Integer?) -> String
def rbs_comments_to_sorbet_sigs(ruby_contents, max_line_length: nil)
Translate.rbs_comments_to_sorbet_sigs(ruby_contents, file: "test.rb", max_line_length: max_line_length)
#: (String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> String
def rbs_comments_to_sorbet_sigs(ruby_contents, max_line_length: nil, overloads_strategy: :translate_all)
Translate.rbs_comments_to_sorbet_sigs(
ruby_contents,
file: "test.rb",
max_line_length: max_line_length,
overloads_strategy: overloads_strategy,
)
end
end
end
Expand Down
Loading