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
6 changes: 4 additions & 2 deletions lib/rbi/rbs/method_type_translator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ def initialize(method, options: HumanReadableOptions.default)

#: (::RBS::MethodType) -> void
def visit(type)
type.type_params.each do |param|
result.type_params << param.name
unless @options.erase_generic_types?
type.type_params.each do |param|
result.type_params << param.name
end
end

visit_function_type(type.type)
Expand Down
66 changes: 37 additions & 29 deletions lib/rbi/rbs/type_translator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def translate(type)
when ::RBS::Types::Bases::Void
Type.void
when ::RBS::Types::ClassSingleton
type_parameter = type.args.first ? translate(type.args.first) : nil
type_arg = type.args.first
type_parameter = translate(type_arg) if type_arg && !@options.erase_generic_types?
Type.class_of(Type.simple(type.name.to_s), type_parameter)
when ::RBS::Types::ClassInstance
translate_class_instance(type)
Expand Down Expand Up @@ -107,7 +108,7 @@ def translate(type)
when ::RBS::Types::UntypedFunction
Type.proc.params(arg0: Type.untyped).returns(Type.untyped)
when ::RBS::Types::Variable
Type.type_parameter(type.name)
@options.erase_generic_types? ? Type.anything : Type.type_parameter(type.name)

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.

Inline with my other comment, I think these should always stay

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.

Ok, I agree that we can replace method type variables with T.anyting, but nitpick: Can we make this a normal if/else. Usage of the ternary operator is really rare in Ruby:

Suggested change
@options.erase_generic_types? ? Type.anything : Type.type_parameter(type.name)
if @options.erase_generic_types?
Type.anything
else
Type.type_parameter(type.name)
end

else
type #: absurd
end
Expand All @@ -129,10 +130,14 @@ def translate_type_alias(type)

#: (::RBS::Types::ClassInstance) -> Type
def translate_class_instance(type)
return Type.simple(type.name.to_s) if type.args.empty?
type_name = type.name.to_s
return Type.simple(type_name) if type.args.empty?

type_name = translate_t_generic_type(type.name.to_s)
Type.generic(type_name, *type.args.map { |arg| translate(arg) })
if @options.erase_generic_types?
Type.simple(erase_t_generic_type(type_name))
else
Type.generic(translate_t_generic_type(type_name), *type.args.map { |arg| translate(arg) })
end
end

#: (::RBS::Types::Function) -> Type
Expand Down Expand Up @@ -182,32 +187,35 @@ def translate_function(type)
proc
end

RUNTIME_GENERIC_TYPES = [
"Array",
"Class",
"Enumerable",
"Enumerator",
"Enumerator::Chain",
"Enumerator::Lazy",
"Hash",
"Module",
"Set",
"Range",
].freeze #: Array[String]
Comment thread
paracycle marked this conversation as resolved.

GENERIC_TYPE_TO_SORBET_GENERIC_TYPE = RUNTIME_GENERIC_TYPES.flat_map do |type|
[[type, "::T::#{type}"], ["::#{type}", "::T::#{type}"]]
end.to_h.freeze #: Hash[String, String]

SORBET_GENERIC_TYPE_TO_GENERIC_TYPE = RUNTIME_GENERIC_TYPES.flat_map do |type|
[["T::#{type}", "::#{type}"], ["::T::#{type}", "::#{type}"]]
end.to_h.freeze #: Hash[String, String]

#: (String type_name) -> String
def translate_t_generic_type(type_name)
case type_name.delete_prefix("::")
when "Array"
"::T::Array"
when "Class"
"::T::Class"
when "Enumerable"
"::T::Enumerable"
when "Enumerator"
"::T::Enumerator"
when "Enumerator::Chain"
"::T::Enumerator::Chain"
when "Enumerator::Lazy"
"::T::Enumerator::Lazy"
when "Hash"
"::T::Hash"
when "Module"
"::T::Module"
when "Set"
"::T::Set"
when "Range"
"::T::Range"
else
type_name
end
GENERIC_TYPE_TO_SORBET_GENERIC_TYPE.fetch(type_name, type_name)
end

#: (String type_name) -> String
def erase_t_generic_type(type_name)

@dejmedus dejmedus Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Similarly to how we add the prefix to translate generic types, we probably want to strip prefixes and use the actual runtime type when erasing. Otherwise something like #: (String component_name, T::Array[Symbol] includes) -> void can error at runtime

SORBET_GENERIC_TYPE_TO_GENERIC_TYPE.fetch(type_name, type_name)
end
Comment thread
paracycle marked this conversation as resolved.
end
end
Expand Down
6 changes: 6 additions & 0 deletions rbi/rbi.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,9 @@ class RBI::RBS::TypeTranslator

private

sig { params(type_name: ::String).returns(::String) }
def erase_t_generic_type(type_name); end

sig { params(type: ::RBS::Types::ClassInstance).returns(::RBI::Type) }
def translate_class_instance(type); end

Expand All @@ -1469,9 +1472,12 @@ class RBI::RBS::TypeTranslator
end
end

RBI::RBS::TypeTranslator::GENERIC_TYPE_TO_SORBET_GENERIC_TYPE = T.let(T.unsafe(nil), Hash)
RBI::RBS::TypeTranslator::HumanReadableOptions = RBI::RBS::MethodTypeTranslator::HumanReadableOptions
RBI::RBS::TypeTranslator::Options = RBI::RBS::MethodTypeTranslator::Options
RBI::RBS::TypeTranslator::RUNTIME_GENERIC_TYPES = T.let(T.unsafe(nil), Array)
RBI::RBS::TypeTranslator::RbsType = T.type_alias { T.any(::RBS::Types::Alias, ::RBS::Types::Bases::Any, ::RBS::Types::Bases::Bool, ::RBS::Types::Bases::Bottom, ::RBS::Types::Bases::Class, ::RBS::Types::Bases::Instance, ::RBS::Types::Bases::Nil, ::RBS::Types::Bases::Self, ::RBS::Types::Bases::Top, ::RBS::Types::Bases::Void, ::RBS::Types::ClassInstance, ::RBS::Types::ClassSingleton, ::RBS::Types::Function, ::RBS::Types::Interface, ::RBS::Types::Intersection, ::RBS::Types::Literal, ::RBS::Types::Optional, ::RBS::Types::Proc, ::RBS::Types::Record, ::RBS::Types::Tuple, ::RBS::Types::Union, ::RBS::Types::UntypedFunction, ::RBS::Types::Variable) }
RBI::RBS::TypeTranslator::SORBET_GENERIC_TYPE_TO_GENERIC_TYPE = T.let(T.unsafe(nil), Hash)

class RBI::RBSComment < ::RBI::Comment
sig { params(other: ::Object).returns(T::Boolean) }
Expand Down
45 changes: 42 additions & 3 deletions test/rbi/rbs/method_type_translator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,51 @@ def test_translate_generic_singleton
assert_equal(Type.class_of(Type.simple("Foo"), Type.simple("Bar")), sig.return_type)
end

def test_erase_generic_types_replaces_method_type_parameters
sig = translate(
"[T, U] (Array[T], T?, T | Integer, [T, Integer], singleton(Foo)[T]) { (T) -> U } -> U?",
Method.new("foo", params: [
ReqParam.new("array"),
ReqParam.new("value"),
ReqParam.new("fallback"),
ReqParam.new("pair"),
ReqParam.new("klass"),
BlockParam.new("block"),
]),
erase_generic_types: true,
)

assert_empty(sig.type_params)
assert_equal(
[
SigParam.new("array", Type.simple("Array")),
SigParam.new("value", Type.nilable(Type.anything)),
SigParam.new("fallback", Type.any(Type.anything, Type.simple("Integer"))),
SigParam.new("pair", Type.tuple([Type.anything, Type.simple("Integer")])),
SigParam.new("klass", Type.class_of(Type.simple("Foo"))),
SigParam.new(
"block",
Type.proc
.params(arg0: Type.anything)
.returns(Type.anything),
),
],
sig.params,
)
assert_equal(Type.nilable(Type.anything), sig.return_type)
end

private

#: (String, Method) -> RBI::Sig
def translate(rbs_string, method)
#: (String, Method, ?erase_generic_types: bool) -> RBI::Sig
def translate(rbs_string, method, erase_generic_types: false)
node = ::RBS::Parser.parse_method_type(rbs_string, require_eof: true)
RBS::MethodTypeTranslator.translate(method, node)

options = MethodTypeTranslator::HumanReadableOptions.new(erase_generic_types:)

translator = RBS::MethodTypeTranslator.new(method, options:)
translator.visit(node)
translator.result
end
end
end
Expand Down
31 changes: 28 additions & 3 deletions test/rbi/rbs/type_translator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,30 @@ def test_translate_class_singleton
assert_equal(Type.class_of(Type.simple("Foo"), Type.simple("Bar")), translate("singleton(Foo)[Bar]"))
end

def test_erase_generic_types_keeps_non_generics
assert_equal(Type.simple("Foo"), translate("Foo", erase_generic_types: true))
assert_equal(Type.simple("::Foo::Bar"), translate("::Foo::Bar", erase_generic_types: true))
end

def test_erase_generic_types_removes_generic_types
simple_foo = Type.simple("Foo")
class_foo = Type.class_of(simple_foo)
simple_array = Type.simple("Array")
root_array = Type.simple("::Array")

assert_equal(simple_foo, translate("Foo[Bar]", erase_generic_types: true))
assert_equal(simple_foo, translate("Foo[Bar, ::Baz]", erase_generic_types: true))
assert_equal(class_foo, translate("singleton(Foo)", erase_generic_types: true))
assert_equal(class_foo, translate("singleton(Foo)[Bar]", erase_generic_types: true))
assert_equal(simple_array, translate("Array[Foo]", erase_generic_types: true))
assert_equal(root_array, translate("T::Array[Foo]", erase_generic_types: true))
assert_equal(root_array, translate("::T::Array[Foo]", erase_generic_types: true))
assert_equal(root_array, translate("::Array[Bar]", erase_generic_types: true))
assert_equal(Type.simple("::Hash"), translate("::Hash[Foo, Bar]", erase_generic_types: true))
assert_equal(Type.simple("::Foo"), translate("::Foo[Bar]", erase_generic_types: true))
assert_equal(Type.simple("::Types::Foo"), translate("::Types::Foo[Bar]", erase_generic_types: true))
end

def test_translate_interface
assert_equal(Type.untyped, translate("_Foo"))
end
Expand Down Expand Up @@ -170,10 +194,11 @@ def test_translate_untyped_function

private

#: (String) -> RBI::Type
def translate(rbs_string)
#: (String, ?erase_generic_types: bool) -> RBI::Type
def translate(rbs_string, erase_generic_types: false)
node = ::RBS::Parser.parse_type(rbs_string, require_eof: true)
RBS::TypeTranslator.new.translate(node)
options = MethodTypeTranslator::HumanReadableOptions.new(erase_generic_types:)
RBS::TypeTranslator.new(options:).translate(node)
end
end
end
Expand Down
Loading