Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def initialize(
@translate_abstract_methods = options.translate_abstract_methods #: bool
@options = options #: Options
@type_translator = RBI::RBS::TypeTranslator.new(options: options.rbi_options) #: RBI::RBS::TypeTranslator
@scopes = [] #: Array[String | Symbol]
end

# @override
Expand All @@ -45,23 +46,23 @@ def visit_program_node(node)
def visit_class_node(node)
apply_class_annotations(node)

super
with_scope(node.constant_path.slice) { super }
end

# @override
#: (Prism::ModuleNode node) -> void
def visit_module_node(node)
apply_class_annotations(node)

super
with_scope(node.constant_path.slice) { super }
end

# @override
#: (Prism::SingletonClassNode node) -> void
def visit_singleton_class_node(node)
apply_class_annotations(node)

super
with_scope(node.expression.is_a?(Prism::SelfNode) ? :singleton_of_self : :singleton_of_other) { super }
end

# @override
Expand Down Expand Up @@ -182,6 +183,14 @@ def rewrite_def(def_node, comments)
sig.without_runtime = true
end

# A sig on `C.[]` that mentions a generic application of `C` calls `C.[]` while sorbet-runtime builds it,
# and the wrapped `C.[]` builds its sig before running, so evaluating the sig recurses forever.
# This happens when a class defines its own `[]` in place of `T::Generic#[]`, typically after dropping
# `sorbet-runtime` as a dependency.
if applies_own_generic_in_singleton_bracket_sig?(def_node, sig)
sig.without_runtime = true
end

@rewriter << Source::Replace.new(
signature.location.start_offset,
signature.location.end_offset,
Expand All @@ -194,6 +203,58 @@ def rewrite_def(def_node, comments)
end
end

#: (String | Symbol) { -> void } -> void
def with_scope(scope, &block)
@scopes << scope
block.call
ensure
@scopes.pop
end

#: (Prism::DefNode, RBI::Sig) -> bool
def applies_own_generic_in_singleton_bracket_sig?(def_node, sig)
return false unless def_node.name == :[]

receiver = def_node.receiver
singleton = if receiver
receiver.is_a?(Prism::SelfNode)
else
@scopes.last == :singleton_of_self
end
return false unless singleton

scope_names = @scopes.grep(String)
return false if scope_names.empty?

owner_name = scope_names.map { |name| name.delete_prefix("::") }.join("::")

types = sig.params.map(&:type) + [sig.return_type]
types.any? { |type| type.is_a?(RBI::Type) && applies_generic?(type, owner_name) }
end

#: (RBI::Type, String) -> bool
def applies_generic?(type, owner_name)
case type
when RBI::Type::Generic
name = type.name.delete_prefix("::")
return true if owner_name == name || owner_name.end_with?("::#{name}")

type.params.any? { |param| applies_generic?(param, owner_name) }
when RBI::Type::Class, RBI::Type::Module, RBI::Type::Nilable
applies_generic?(type.type, owner_name)
when RBI::Type::Composite, RBI::Type::Tuple
type.types.any? { |member| applies_generic?(member, owner_name) }
when RBI::Type::Shape
type.types.values.any? { |member| applies_generic?(member, owner_name) }
when RBI::Type::Proc
[*type.proc_params.values, type.proc_returns, type.proc_bind].compact.any? do |member|
applies_generic?(member, owner_name)
end
else
false
end
end

#: (Array[Spoom::RBS::Signature], method_name: String, location: String) -> Array[Spoom::RBS::Signature]
def apply_overloads_strategy(signatures, method_name:, location:)
return signatures if signatures.size <= 1
Expand Down
104 changes: 104 additions & 0 deletions test/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,110 @@ def singleton_method_added(m); end
)
end

def test_translate_to_rbi_singleton_bracket_applying_own_generic_is_without_runtime
assert_rewrites_rbs(
from: <<~RUBY,
module A
#: [E]
class B
class << self
#: [U] (*Class[U]) -> Class[B[U]]
def [](*types) = self
end

#: [U] (Class[U]) -> A::B[U]?
def self.[](type) = nil
end
end
RUBY

to_pretty_format_for_humans: <<~RUBY,
module A
class B
extend ::T::Generic

E = type_member

class << self
::T::Sig::WithoutRuntime.sig { type_parameters(:U).params(types: ::T::Class[::T.type_parameter(:U)]).returns(::T::Class[B[::T.type_parameter(:U)]]) }
def [](*types) = self
end

::T::Sig::WithoutRuntime.sig { type_parameters(:U).params(type: ::T::Class[::T.type_parameter(:U)]).returns(::T.nilable(A::B[::T.type_parameter(:U)])) }
def self.[](type) = nil
end
end
RUBY

to_line_matched_format_for_machines: <<~RUBY,
module A
# RBS_WRITTEN_ANNOTATION: [E]
class B; extend ::T::Generic; E = type_member
class << self
::T::Sig::WithoutRuntime.sig { type_parameters(:U).params(types: ::T::Class[::T.type_parameter(:U)]).returns(::T::Class[B[::T.type_parameter(:U)]]) }
def [](*types) = self
end

::T::Sig::WithoutRuntime.sig { type_parameters(:U).params(type: ::T::Class[::T.type_parameter(:U)]).returns(::T.nilable(A::B[::T.type_parameter(:U)])) }
def self.[](type) = nil
end
end
RUBY
)
end

def test_translate_to_rbi_keeps_runtime_sigs_that_do_not_call_their_own_bracket_method
assert_rewrites_rbs(
from: <<~RUBY,
class A
class << self
#: (*untyped) -> self
def [](*types) = self

#: -> A[Integer]
def build = new
end

#: (Integer) -> A[Integer]
def [](index) = self

#: -> Array[A]
def self.all = []
end

class C
#: (Class[untyped]) -> A[Integer]
def self.[](type) = A.new
end
RUBY

to_pretty_format_for_humans: <<~RUBY,
class A
class << self
sig { params(types: ::T.untyped).returns(::T.self_type) }
def [](*types) = self

sig { returns(A[Integer]) }
def build = new
end

sig { params(index: Integer).returns(A[Integer]) }
def [](index) = self

sig { returns(::T::Array[A]) }
def self.all = []
end

class C
sig { params(type: ::T::Class[::T.untyped]).returns(A[Integer]) }
def self.[](type) = A.new
end
RUBY

to_line_matched_format_for_machines: :same_as_pretty_output,
)
end

def test_translate_to_rbi_singleton_method_sigs
assert_rewrites_rbs(
from: <<~RUBY,
Expand Down
Loading