From 9589b5af36ef69fc8ce2aaeda20652a7c516c623 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 4 Mar 2026 08:52:33 -0500 Subject: [PATCH 01/53] Parse method return types from inline RBS --- lib/solargraph/pin/method.rb | 22 ++++++++++++++++++++-- spec/pin/method_spec.rb | 13 +++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 09d681c67..85c6bf396 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -143,7 +143,7 @@ def symbol_kind end def return_type - @return_type ||= ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) + @return_type ||= generate_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) end # @param parameters [::Array] @@ -451,7 +451,8 @@ def rest_of_stack api_map attr_writer :block, :signature_help, :documentation, :return_type - # @sg-ignore Need to add nil check here + attr_writer :return_type + def dodgy_visibility_source? # as of 2025-03-12, the RBS generator used for # e.g. activesupport did not understand 'private' markings @@ -721,6 +722,23 @@ def concat_example_tags .join("\n") .concat("```\n") end + + def generate_from_inline_rbs + return nil if inline_rbs.empty? + + method_type = RBS::Parser.parse_method_type(inline_rbs) + type_name = method_type.type.return_type.name.to_s + ComplexType.try_parse(method_type.type.return_type.name.to_s) + rescue RBS::ParsingError + nil + end + + def inline_rbs + comments.lines + .select { |line| line.start_with?(': ') } + .map { |line| line[2..].strip } + .join("\n") + end end end end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index c109746af..b1165a05e 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -632,4 +632,17 @@ def bar expect(pin.overloads).to be_empty end end + + context 'with inline rbs' do + it 'sets return types' do + source = Solargraph::Source.load_string(%( + #: () -> String + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('String') + end + end end From 590ef56befdde9963bb7abe9d7a3b68b691cd715 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 5 Mar 2026 07:51:38 -0500 Subject: [PATCH 02/53] RbsToComplex module --- lib/solargraph.rb | 1 + lib/solargraph/pin/method.rb | 3 +- lib/solargraph/rbs_map/conversions.rb | 211 ++++++-------------------- lib/solargraph/rbs_to_complex.rb | 102 +++++++++++++ spec/pin/method_spec.rb | 35 ++++- 5 files changed, 185 insertions(+), 167 deletions(-) create mode 100644 lib/solargraph/rbs_to_complex.rb diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 75c454dde..067b067e0 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,6 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' + autoload :RbsToComplex, 'solargraph/rbs_to_complex' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 85c6bf396..dbe656c39 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -727,8 +727,7 @@ def generate_from_inline_rbs return nil if inline_rbs.empty? method_type = RBS::Parser.parse_method_type(inline_rbs) - type_name = method_type.type.return_type.name.to_s - ComplexType.try_parse(method_type.type.return_type.name.to_s) + RbsToComplex.convert(method_type.type.return_type) rescue RBS::ParsingError nil end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 3caf13162..ff9503def 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -274,7 +274,10 @@ def class_decl_to_pin decl # @type [Hash{String => ComplexType, ComplexType::UniqueType}] generic_defaults = {} decl.type_params.each do |param| - generic_defaults[param.name.to_s] = other_type_to_type param.default_type if param.default_type + if param.default_type + complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted + generic_defaults[param.name.to_s] = complex_type + end end class_name = fqns(decl.name) @@ -406,9 +409,8 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - target_type = other_type_to_type(decl.type) - constant_name = fqns(decl.name) - pins.push create_constant(constant_name, target_type, decl.comment&.string, decl) + tag = RbsTranslator.to_complex_type(decl.type).to_s + pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end # @param decl [RBS::AST::Declarations::Global] @@ -423,7 +425,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -590,77 +592,10 @@ def location_decl_to_pin_location location # @param pin [Pin::Method] # @return [Array(Array, ComplexType)] def parts_of_function type, pin - type_location = pin.type_location - if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) - return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, - type_location: type_location)], - method_type_to_type(type) - ] - end - - parameters = [] - arg_num = -1 - type.type.required_positionals.each do |param| - # @sg-ignore Unresolved call to name - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - source: :rbs, type_location: type_location) - end - type.type.optional_positionals.each do |param| - # @sg-ignore Unresolved call to name - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - type_location: type_location, - source: :rbs) - end - if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) - rest_positional_type = ComplexType::UniqueType.new('Array', - [], - [inner_rest_positional_type], - rooted: true, parameters_type: :list) - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, - source: :rbs, type_location: type_location, - return_type: rest_positional_type) - end - type.type.trailing_positionals.each do |param| - # @sg-ignore Unresolved call to name - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, - type_location: type_location) - end - type.type.required_keywords.each do |orig, param| - # @sg-ignore Unresolved call to to_s - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - source: :rbs, type_location: type_location) - end - type.type.optional_keywords.each do |orig, param| - # @sg-ignore Unresolved call to to_s - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - type_location: type_location, - source: :rbs) - end - if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, - name: type.type.rest_keywords.name.to_s, closure: pin, - source: :rbs, type_location: type_location) - end - - return_type = method_type_to_type(type) - [parameters, return_type] + [ + RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), + extract_method_type_return_type(type).force_rooted + ] end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -681,7 +616,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -712,13 +647,13 @@ def attr_writer_to_pin decl, closure, context pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: other_type_to_type(decl.type), + return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tags = other_type_to_type(decl.type).rooted_tags - pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tags)) + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -742,7 +677,7 @@ def ivar_to_pin decl, closure comments: decl.comment&.string, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -759,7 +694,7 @@ def cvar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -776,7 +711,7 @@ def civar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -841,90 +776,38 @@ def alias_to_pin decl, closure ) end - # @param type [RBS::MethodType, RBS::Types::Block] - # @return [ComplexType, ComplexType::UniqueType] - def method_type_to_type type - other_type_to_type type.type.return_type - end + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + } - # @param type [RBS::Types::Bases::Base,Object] RBS type object. - # Note: Generally these extend from RBS::Types::Bases::Base, - # but not all. + # Extract a ComplexType from a MethodType's return type. # - # @return [ComplexType, ComplexType::UniqueType] - def other_type_to_type type - case type - when RBS::Types::Optional - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.new([other_type_to_type(type.type), - ComplexType::UniqueType::NIL]) - when RBS::Types::Bases::Any - ComplexType::UNDEFINED - when RBS::Types::Bases::Bool - ComplexType::BOOLEAN - when RBS::Types::Tuple - # @sg-ignore flow based typing needs to understand case when class pattern - tuple_types = type.types.map { |t| other_type_to_type(t) } - ComplexType::UniqueType.new('Array', [], tuple_types, rooted: true, parameters_type: :fixed) - when RBS::Types::Literal - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.try_parse(type.literal.inspect).force_rooted - when RBS::Types::Union - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.new(type.types.map { |t| other_type_to_type(t) }) - when RBS::Types::Record - # @todo Better record support - ComplexType::UniqueType.new('Hash', rooted: true) - when RBS::Types::Bases::Nil - ComplexType::NIL - when RBS::Types::Bases::Self - ComplexType::SELF - when RBS::Types::Bases::Void - ComplexType::VOID - when RBS::Types::Variable - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.parse("generic<#{type.name}>").force_rooted - when RBS::Types::ClassInstance # && !type.args.empty? - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name, type.args) - when RBS::Types::Bases::Instance - ComplexType::SELF - when RBS::Types::Bases::Top - # top is the most super superclass - ComplexType::UniqueType.new('BasicObject', rooted: true) - when RBS::Types::Bases::Bottom - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - ComplexType::UNDEFINED - when RBS::Types::Intersection - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.new(type.types.map { |member| other_type_to_type(member) }) - when RBS::Types::Proc - ComplexType::UniqueType.new('Proc', rooted: true) - when RBS::Types::Alias - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name, type.args) - when RBS::Types::Interface - # represents a mix-in module which can be considered a - # subtype of a consumer of it - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name, type.args) - when RBS::Types::ClassSingleton - # e.g., singleton(String) - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name) + # This method will convert type aliases to concrete types. + # + # @param type [RBS::MethodType] + # @return [ComplexType] + def extract_method_type_return_type type + if type_aliases.key?(type.type.return_type.to_s) + RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) + else + RbsTranslator.to_complex_type(type.type.return_type) + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else - # RBS doesn't provide a common base class for its type AST nodes - # - # @sg-ignore all types should include location - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - ComplexType::UNDEFINED + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) end end diff --git a/lib/solargraph/rbs_to_complex.rb b/lib/solargraph/rbs_to_complex.rb new file mode 100644 index 000000000..46051bf45 --- /dev/null +++ b/lib/solargraph/rbs_to_complex.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module Solargraph + # Convert RBS::Types to ComplexTypes. + # + module RbsToComplex + # @param type [RBS::Types::Bases::Base] + # @return [ComplexType] + def self.convert type + tag = type_to_tag(type) + ComplexType.try_parse(tag) + end + + class << self + private + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def type_to_tag type + if type.is_a?(RBS::Types::Optional) + "#{type_to_tag(type.type)}, nil" + elsif type.is_a?(RBS::Types::Bases::Any) + 'undefined' + elsif type.is_a?(RBS::Types::Bases::Bool) + 'Boolean' + elsif type.is_a?(RBS::Types::Tuple) + "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" + elsif type.is_a?(RBS::Types::Literal) + type.literal.inspect + elsif type.is_a?(RBS::Types::Union) + type.types.map { |t| type_to_tag(t) }.join(', ') + elsif type.is_a?(RBS::Types::Record) + # @todo Better record support + 'Hash' + elsif type.is_a?(RBS::Types::Bases::Nil) + 'nil' + elsif type.is_a?(RBS::Types::Bases::Self) + 'self' + elsif type.is_a?(RBS::Types::Bases::Void) + 'void' + elsif type.is_a?(RBS::Types::Variable) + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Bases::Instance) + 'self' + elsif type.is_a?(RBS::Types::Bases::Top) + # top is the most super superclass + 'BasicObject' + elsif type.is_a?(RBS::Types::Bases::Bottom) + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + 'undefined' + elsif type.is_a?(RBS::Types::Intersection) + type.types.map { |member| type_to_tag(member) }.join(', ') + elsif type.is_a?(RBS::Types::Proc) + 'Proc' + elsif type.is_a?(RBS::Types::Alias) + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Interface) + # represents a mix-in module which can be considered a + # subtype of a consumer of it + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::ClassSingleton) + # e.g., singleton(String) + type_tag(type.name) + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [String] + def type_tag(type_name, type_args = []) + build_type(type_name, type_args).tags + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RbsMap::Conversions::RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |a| type_to_tag(a) }.map do |t| + ComplexType.try_parse(t).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + end + end +end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index b1165a05e..bb34454a4 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -634,7 +634,7 @@ def bar end context 'with inline rbs' do - it 'sets return types' do + it 'sets instance return types' do source = Solargraph::Source.load_string(%( #: () -> String def foo; end @@ -644,5 +644,38 @@ def foo; end pin = api_map.get_path_pins('#foo').first expect(pin.return_type.to_s).to eq('String') end + + it 'sets parameterized instance return types' do + source = Solargraph::Source.load_string(%( + #: () -> Array[String] + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Array') + end + + it 'sets parametrized instance return types' do + source = Solargraph::Source.load_string(%( + #: () -> Array[String] + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Array') + end + + it 'sets YARD conventional return types' do + source = Solargraph::Source.load_string(%( + #: () -> bool + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Boolean') + end end end From 52f179a1dc3c0f200a47ccf9344a26d2d66b2dfe Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 10:38:36 -0500 Subject: [PATCH 03/53] RbsTranslator --- lib/solargraph.rb | 2 +- lib/solargraph/rbs_to_complex.rb | 102 ---------------- lib/solargraph/rbs_translator.rb | 202 +++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 103 deletions(-) delete mode 100644 lib/solargraph/rbs_to_complex.rb create mode 100644 lib/solargraph/rbs_translator.rb diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 067b067e0..73c426c2f 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,7 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' - autoload :RbsToComplex, 'solargraph/rbs_to_complex' + autoload :RbsTranslator, 'solargraph/rbs_translator' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/lib/solargraph/rbs_to_complex.rb b/lib/solargraph/rbs_to_complex.rb deleted file mode 100644 index 46051bf45..000000000 --- a/lib/solargraph/rbs_to_complex.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - # Convert RBS::Types to ComplexTypes. - # - module RbsToComplex - # @param type [RBS::Types::Bases::Base] - # @return [ComplexType] - def self.convert type - tag = type_to_tag(type) - ComplexType.try_parse(tag) - end - - class << self - private - - # @param type [RBS::Types::Bases::Base] - # @return [String] - def type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) - 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) - type.literal.inspect - elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) - # @todo Better record support - 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) - 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) - 'void' - elsif type.is_a?(RBS::Types::Variable) - "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) - 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass - 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) - 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a - # subtype of a consumer of it - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) - # e.g., singleton(String) - type_tag(type.name) - else - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' - end - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [String] - def type_tag(type_name, type_args = []) - build_type(type_name, type_args).tags - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [ComplexType::UniqueType] - def build_type(type_name, type_args = []) - base = RbsMap::Conversions::RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted - end - if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) - else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) - end - end - end - end -end diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb new file mode 100644 index 000000000..a17bfd968 --- /dev/null +++ b/lib/solargraph/rbs_translator.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +module Solargraph + # Convert RBS::Types to ComplexTypes. + # + module RbsTranslator + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + } + + # @param type [RBS::Types::Bases::Base] + # @return [ComplexType] + def self.to_complex_type(type) + tag = type_to_tag(type) + ComplexType.try_parse(tag) + end + + def self.to_parameter_pin(param_type, name, closure) + Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: closure, return_type: RbsTranslator.to_complex_type(param_type.type).force_rooted, source: :rbs, type_location: nil) + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def self.build_unique_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map do |a| + RbsTranslator.to_complex_type(a).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + + class << self + private + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def type_to_tag type + if type.is_a?(RBS::Types::Optional) + "#{type_to_tag(type.type)}, nil" + elsif type.is_a?(RBS::Types::Bases::Any) + 'undefined' + elsif type.is_a?(RBS::Types::Bases::Bool) + 'Boolean' + elsif type.is_a?(RBS::Types::Tuple) + "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" + elsif type.is_a?(RBS::Types::Literal) + type.literal.inspect + elsif type.is_a?(RBS::Types::Union) + type.types.map { |t| type_to_tag(t) }.join(', ') + elsif type.is_a?(RBS::Types::Record) + # @todo Better record support + 'Hash' + elsif type.is_a?(RBS::Types::Bases::Nil) + 'nil' + elsif type.is_a?(RBS::Types::Bases::Self) + 'self' + elsif type.is_a?(RBS::Types::Bases::Void) + 'void' + elsif type.is_a?(RBS::Types::Variable) + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Bases::Instance) + 'self' + elsif type.is_a?(RBS::Types::Bases::Top) + # top is the most super superclass + 'BasicObject' + elsif type.is_a?(RBS::Types::Bases::Bottom) + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + 'undefined' + elsif type.is_a?(RBS::Types::Intersection) + type.types.map { |member| type_to_tag(member) }.join(', ') + elsif type.is_a?(RBS::Types::Proc) + 'Proc' + elsif type.is_a?(RBS::Types::Alias) + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Interface) + # represents a mix-in module which can be considered a + # subtype of a consumer of it + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::ClassSingleton) + # e.g., singleton(String) + type_tag(type.name) + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [String] + def type_tag(type_name, type_args = []) + build_type(type_name, type_args).tags + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |a| type_to_tag(a) }.map do |t| + ComplexType.try_parse(t).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + end + + # @param type [RBS::MethodType,RBS::Types::Block] + # @param pin [Pin::Method] + # @return [Array(Array, ComplexType)] + def parts_of_function type, pin + type_location = pin.type_location + if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) + return [ + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], + method_type_to_tag(type).force_rooted + ] + end + + parameters = [] + arg_num = -1 + type.type.required_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + # @sg-ignore RBS generic type understanding issue + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) + end + type.type.optional_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_positionals + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" + inner_rest_positional_type = + RbsTranslator.to_complex_type(type.type.rest_positionals.type) + rest_positional_type = ComplexType::UniqueType.new('Array', + [], + [inner_rest_positional_type], + rooted: true, parameters_type: :list) + parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, + source: :rbs, type_location: type_location, + return_type: rest_positional_type,) + end + type.type.trailing_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) + end + type.type.required_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, + source: :rbs, type_location: type_location) + end + type.type.optional_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_keywords + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, + source: :rbs, type_location: type_location) + end + + return_type = method_type_to_tag(type).force_rooted + [parameters, return_type] + end + end +end From b960ac06fb368b2f0de8845f20313dcf97de955b Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 10:38:50 -0500 Subject: [PATCH 04/53] Generate signatures from inline RBS --- lib/solargraph/pin/method.rb | 27 +++++++----- lib/solargraph/rbs_map/conversions.rb | 63 +++++++++------------------ spec/pin/method_spec.rb | 15 +++++++ 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index dbe656c39..1b31a90bf 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -189,16 +189,22 @@ def generate_signature parameters, return_type # @return [::Array] def signatures @signatures ||= begin - top_type = generate_complex_type - result = [] - result.push generate_signature(parameters, top_type) if top_type.defined? - unless overloads.empty? - result.concat(overloads.map do |meth| - generate_signature(meth.parameters, meth.return_type) - end) + if inline_rbs.empty? + top_type = generate_complex_type + result = [] + result.push generate_signature(parameters, top_type) if top_type.defined? + result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? + result + else + these = [] + # @type [RBS::MethodType] + method_type = RBS::Parser.parse_method_type(inline_rbs) + method_type.type.required_positionals.each_with_index do |pos, idx| + these.push RbsTranslator.to_parameter_pin(pos, parameter_names[idx] || "arg_#{idx}", self) + end + [generate_signature(these, return_type)] end - result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? - result end end @@ -725,9 +731,8 @@ def concat_example_tags def generate_from_inline_rbs return nil if inline_rbs.empty? - method_type = RBS::Parser.parse_method_type(inline_rbs) - RbsToComplex.convert(method_type.type.return_type) + RbsTranslator.to_complex_type(method_type.type.return_type) rescue RBS::ParsingError nil end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index ff9503def..a2d3bdbaf 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -184,8 +184,8 @@ def build_type type_name, type_args = [] # @param closure [Pin::Namespace] # @return [void] def convert_self_type_to_pins decl, closure - type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(decl.name, decl.args) + generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( name: type.name, type_location: location_decl_to_pin_location(decl.location), @@ -275,8 +275,7 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted - generic_defaults[param.name.to_s] = complex_type + generic_defaults[param.name.to_s] = RbsTranslator.to_complex_type(param.default_type) end end @@ -299,8 +298,9 @@ def class_decl_to_pin decl ) pins.push class_pin if decl.super_class - type = build_type(decl.super_class.name, decl.super_class.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(decl.super_class.name, decl.super_class.args) + generic_values = type.all_params.map(&:to_s) + superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, @@ -409,7 +409,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsTranslator.to_complex_type(decl.type).to_s + tag = RbsTranslator.to_complex_type(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -720,8 +720,8 @@ def civar_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def include_to_pin decl, closure - type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(decl.name, decl.args) + generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), @@ -776,39 +776,16 @@ def alias_to_pin decl, closure ) end - RBS_TO_YARD_TYPE = { - 'bool' => 'Boolean', - 'string' => 'String', - 'int' => 'Integer', - 'untyped' => '', - 'NilClass' => 'nil' - } - - # Extract a ComplexType from a MethodType's return type. - # - # This method will convert type aliases to concrete types. - # # @param type [RBS::MethodType] - # @return [ComplexType] - def extract_method_type_return_type type - if type_aliases.key?(type.type.return_type.to_s) - RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) - else - RbsTranslator.to_complex_type(type.type.return_type) - end - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [ComplexType::UniqueType] - def build_type(type_name, type_args = []) - base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } - if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) - else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) - end + # @return [String] + def method_type_to_tag type + RbsTranslator.to_complex_type( + if type_aliases.key?(type.type.return_type.to_s) + type_aliases[type.type.return_type.to_s].type + else + type.type.return_type + end + ) end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] @@ -819,8 +796,8 @@ def add_mixins decl, namespace decl.each_mixin do |mixin| # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend - type = build_type(mixin.name, mixin.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(mixin.name, mixin.args) + generic_values = type.all_params.map(&:to_s) pins.push klass.new( name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(mixin.location), diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index bb34454a4..23e75f79d 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -677,5 +677,20 @@ def foo; end pin = api_map.get_path_pins('#foo').first expect(pin.return_type.to_s).to eq('Boolean') end + + it 'sets signatures' do + source = Solargraph::Source.load_string(%( + #: (String) -> bool + def foo(bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + expect(pin.signatures.first.return_type.to_s).to eq('Boolean') + end end end From 5aba72cc53ffdcd597385c26e58d1a373d46d3ac Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 23:35:28 -0500 Subject: [PATCH 05/53] Generate all parameters for signatures --- lib/solargraph/pin/method.rb | 35 +++--- lib/solargraph/rbs_map/conversions.rb | 132 ++++++++-------------- lib/solargraph/rbs_translator.rb | 154 +++++++++++++------------- spec/pin/method_spec.rb | 104 ++++++++++++++++- 4 files changed, 245 insertions(+), 180 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 1b31a90bf..86e851565 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -143,7 +143,7 @@ def symbol_kind end def return_type - @return_type ||= generate_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) + @return_type ||= return_type_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) end # @param parameters [::Array] @@ -190,20 +190,9 @@ def generate_signature parameters, return_type def signatures @signatures ||= begin if inline_rbs.empty? - top_type = generate_complex_type - result = [] - result.push generate_signature(parameters, top_type) if top_type.defined? - result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? - result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? - result + signatures_from_yard else - these = [] - # @type [RBS::MethodType] - method_type = RBS::Parser.parse_method_type(inline_rbs) - method_type.type.required_positionals.each_with_index do |pos, idx| - these.push RbsTranslator.to_parameter_pin(pos, parameter_names[idx] || "arg_#{idx}", self) - end - [generate_signature(these, return_type)] + signatures_from_inline_rbs end end end @@ -729,7 +718,7 @@ def concat_example_tags .concat("```\n") end - def generate_from_inline_rbs + def return_type_from_inline_rbs return nil if inline_rbs.empty? method_type = RBS::Parser.parse_method_type(inline_rbs) RbsTranslator.to_complex_type(method_type.type.return_type) @@ -737,6 +726,22 @@ def generate_from_inline_rbs nil end + def signatures_from_inline_rbs + method_type = RBS::Parser.parse_method_type(inline_rbs) + [RbsTranslator.to_signature(method_type, self, parameter_names)] + rescue RBS::ParsingError + signatures_from_yard + end + + def signatures_from_yard + top_type = generate_complex_type + result = [] + result.push generate_signature(parameters, top_type) if top_type.defined? + result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? + result + end + def inline_rbs comments.lines .select { |line| line.start_with?(': ') } diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index a2d3bdbaf..4b5191eb7 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -187,8 +187,8 @@ def convert_self_type_to_pins decl, closure type = RbsTranslator.build_unique_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( - name: type.name, - type_location: location_decl_to_pin_location(decl.location), + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -288,7 +288,7 @@ def class_decl_to_pin decl name: class_name, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), # @todo some type parameters in core/stdlib have default # values; Solargraph doesn't support that yet as so these # get treated as undefined if not specified @@ -302,7 +302,7 @@ def class_decl_to_pin decl generic_values = type.all_params.map(&:to_s) superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( - type_location: location_decl_to_pin_location(decl.super_class.location), + type_location: RbsTranslator.to_sg_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, name: type.rooted_name, # reference pins use rooted names @@ -318,8 +318,8 @@ def class_decl_to_pin decl def interface_decl_to_pin decl class_pin = Solargraph::Pin::Namespace.new( type: :module, - type_location: location_decl_to_pin_location(decl.location), - name: fqns(decl.name), + type_location: RbsTranslator.to_sg_location(decl.location), + name: decl.name.relative!.to_s, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -338,8 +338,8 @@ def interface_decl_to_pin decl def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, - name: fqns(decl.name), - type_location: location_decl_to_pin_location(decl.location), + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -377,7 +377,7 @@ def create_constant fqns, type, comments, decl, base = nil constant_pin = Solargraph::Pin::Constant.new( name: fqns, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: comments, source: :rbs ) @@ -422,7 +422,7 @@ def global_decl_to_pin decl name: name, closure: closure, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), source: :rbs ) rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags @@ -516,7 +516,7 @@ def method_def_to_pin decl, closure, context pin = Solargraph::Pin::Method.new( name: name, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: decl.comment&.string, scope: final_scope, signatures: [], @@ -531,71 +531,31 @@ def method_def_to_pin decl, closure, context pin.instance_variable_set(:@return_type, ComplexType::VOID) end end - return unless decl.singleton? - final_scope = :class - name = decl.name.to_s - visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - pin = Solargraph::Pin::Method.new( - name: name, - closure: closure, - comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), - visibility: visibility, - scope: final_scope, - signatures: [], - generics: generics, - source: :rbs - ) - pin.signatures.concat method_def_to_sigs(decl, pin) - pins.push pin + if decl.singleton? + final_scope = :class + name = decl.name.to_s + visibility = calculate_method_visibility(decl, context, closure, final_scope, name) + pin = Solargraph::Pin::Method.new( + name: name, + closure: closure, + comments: decl.comment&.string, + type_location: RbsTranslator.to_sg_location(decl.location), + visibility: visibility, + scope: final_scope, + signatures: [], + generics: generics, + source: :rbs + ) + pin.signatures.concat method_def_to_sigs(decl, pin) + pins.push pin + end end # @param decl [RBS::AST::Members::MethodDefinition] # @param pin [Pin::Method] - # @return [void] + # @return [Array] def method_def_to_sigs decl, pin - # @param overload [RBS::AST::Members::MethodDefinition::Overload] - decl.overloads.map do |overload| - # @sg-ignore Wrong argument type for Solargraph::RbsMap::Conversions#location_decl_to_pin_location: - # location expected RBS::Location, nil, received RBS::Location<:type, :type_params>, RBS::AST::Members::Attribute::loc, nil - type_location = location_decl_to_pin_location(overload.method_type.location) - generics = type_parameter_names(overload.method_type) - signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) - rbs_block = overload.method_type.block - block = if rbs_block - block_parameters, block_return_type = parts_of_function(rbs_block, pin) - Pin::Signature.new(generics: generics, parameters: block_parameters, - return_type: block_return_type, source: :rbs, - type_location: type_location, closure: pin) - end - Pin::Signature.new(generics: generics, parameters: signature_parameters, - return_type: signature_return_type, block: block, source: :rbs, - type_location: type_location, closure: pin) - end - end - - # @param location [RBS::Location, nil] - # @return [Solargraph::Location, nil] - def location_decl_to_pin_location location - return nil if location&.name.nil? - - # @sg-ignore flow sensitive typing should handle return nil if location&.name.nil? - start_pos = Position.new(location.start_line - 1, location.start_column) - # @sg-ignore flow sensitive typing should handle return nil if location&.name.nil? - end_pos = Position.new(location.end_line - 1, location.end_column) - range = Range.new(start_pos, end_pos) - # @sg-ignore flow sensitve typing should handle return nil if location&.name.nil? - Location.new(location.name.to_s, range) - end - - # @param type [RBS::MethodType, RBS::Types::Block] - # @param pin [Pin::Method] - # @return [Array(Array, ComplexType)] - def parts_of_function type, pin - [ - RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), - extract_method_type_return_type(type).force_rooted - ] + decl.overloads.map { |overload| RbsTranslator.to_signature(overload.method_type, pin) } end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -608,7 +568,7 @@ def attr_reader_to_pin decl, closure, context visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( name: name, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, comments: decl.comment&.string, scope: final_scope, @@ -632,7 +592,7 @@ def attr_writer_to_pin decl, closure, context final_scope = decl.kind == :instance ? :instance : :class name = "#{decl.name}=" visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - type_location = location_decl_to_pin_location(decl.location) + type_location = RbsTranslator.to_sg_location(decl.location) pin = Solargraph::Pin::Method.new( name: name, type_location: type_location, @@ -673,7 +633,7 @@ def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( name: decl.name.to_s, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: decl.comment&.string, source: :rbs ) @@ -691,7 +651,7 @@ def cvar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), source: :rbs ) rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags @@ -708,7 +668,7 @@ def civar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), source: :rbs ) rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags @@ -723,8 +683,8 @@ def include_to_pin decl, closure type = RbsTranslator.build_unique_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(decl.location), + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -738,9 +698,8 @@ def prepend_to_pin decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Prepend.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(decl.location), - generic_values: generic_values, + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, source: :rbs ) @@ -753,9 +712,8 @@ def extend_to_pin decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Extend.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(decl.location), - generic_values: generic_values, + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, source: :rbs ) @@ -768,7 +726,7 @@ def alias_to_pin decl, closure final_scope = decl.singleton? ? :class : :instance pins.push Solargraph::Pin::MethodAlias.new( name: decl.new_name.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), original: decl.old_name.to_s, closure: closure, scope: final_scope, @@ -799,8 +757,8 @@ def add_mixins decl, namespace type = RbsTranslator.build_unique_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) pins.push klass.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(mixin.location), + name: mixin.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(mixin.location), generic_values: generic_values, closure: namespace, source: :rbs diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index a17bfd968..c1f073452 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Solargraph - # Convert RBS::Types to ComplexTypes. + # Convert RBS types to complex types and pins. # module RbsTranslator RBS_TO_YARD_TYPE = { @@ -19,8 +19,72 @@ def self.to_complex_type(type) ComplexType.try_parse(tag) end - def self.to_parameter_pin(param_type, name, closure) - Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: closure, return_type: RbsTranslator.to_complex_type(param_type.type).force_rooted, source: :rbs, type_location: nil) + # @param param_type [RBS::Types::Function::Param] + # @param name [String] + # @param decl [Symbol] + # @param closure [Pin::Closure] + def self.to_parameter_pin(param_type, name, decl, closure) + return_type = if decl == :restarg + ComplexType.parse('Array') + elsif decl == :kwrestarg + ComplexType.parse('Hash{Symbol => Object}') + else + RbsTranslator.to_complex_type(param_type.type).force_rooted + end + Solargraph::Pin::Parameter.new(decl: decl, name: name, closure: closure, return_type: return_type, source: :rbs, type_location: to_sg_location(param_type.location) || closure.type_location) + end + + # @param method_type [RBS::MethodType] + # @param closure [Pin::Closure] + # @param parameter_names [Array] + # @return [Array] + def self.to_parameter_pins method_type, closure, parameter_names = [] + arg_num = 0 + params = [] + method_type.type.required_positionals.each do |param| + params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :arg, closure) + arg_num += 1 + end + method_type.type.optional_positionals.each do |param| + params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :optarg, closure) + arg_num += 1 + end + if method_type.type.rest_positionals + params.push RbsTranslator.to_parameter_pin(method_type.type.rest_positionals, method_type.type.rest_positionals.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :restarg, closure) + arg_num += 1 + end + method_type.type.required_keywords.each do |param| + params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwarg, closure) + arg_num += 1 + end + method_type.type.optional_keywords.each do |param| + params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwoptarg, closure) + arg_num += 1 + end + if method_type.type.rest_keywords + params.push RbsTranslator.to_parameter_pin(method_type.type.rest_keywords, method_type.type.rest_keywords.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :kwrestarg, closure) + end + params + end + + # @param method_type [RBS::MethodType] + # @param closure [Pin::Closure] + # @param parameter_names [Array] + # @return [Pin::Signature] + def self.to_signature method_type, closure, parameter_names = [] + # there may be edge cases here around different signatures + # having different type params / orders - we may need to match + # this data model and have generics live in signatures to + # handle those correctly + generics = method_type.type_params.map(&:name).map(&:to_s).uniq + parameters = to_parameter_pins(method_type, closure, parameter_names) + return_type = to_complex_type(method_type.type.return_type) + block = if method_type.block + block_parameters = to_parameter_pins(method_type.block, closure) + block_return_type = to_complex_type(method_type.block.type.return_type) + Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: closure.location, closure: closure) + end + Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, source: :rbs, type_location: closure.location, closure: closure) end # @param type_name [RBS::TypeName] @@ -38,6 +102,17 @@ def self.build_unique_type(type_name, type_args = []) end end + # @param location [RBS::Location, nil] + # @return [Solargraph::Location, nil] + def self.to_sg_location(location) + return nil if location&.name.nil? + + start_pos = Position.new(location.start_line - 1, location.start_column) + end_pos = Position.new(location.end_line - 1, location.end_column) + range = Range.new(start_pos, end_pos) + Location.new(location.name.to_s, range) + end + class << self private @@ -125,78 +200,5 @@ def build_type(type_name, type_args = []) end end end - - # @param type [RBS::MethodType,RBS::Types::Block] - # @param pin [Pin::Method] - # @return [Array(Array, ComplexType)] - def parts_of_function type, pin - type_location = pin.type_location - if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) - return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - method_type_to_tag(type).force_rooted - ] - end - - parameters = [] - arg_num = -1 - type.type.required_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) - end - type.type.optional_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = - RbsTranslator.to_complex_type(type.type.rest_positionals.type) - rest_positional_type = ComplexType::UniqueType.new('Array', - [], - [inner_rest_positional_type], - rooted: true, parameters_type: :list) - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, - source: :rbs, type_location: type_location, - return_type: rest_positional_type,) - end - type.type.trailing_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) - end - type.type.required_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - source: :rbs, type_location: type_location) - end - type.type.optional_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, - source: :rbs, type_location: type_location) - end - - return_type = method_type_to_tag(type).force_rooted - [parameters, return_type] - end end end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 23e75f79d..d759b372c 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -678,7 +678,7 @@ def foo; end expect(pin.return_type.to_s).to eq('Boolean') end - it 'sets signatures' do + it 'sets required positional parameters' do source = Solargraph::Source.load_string(%( #: (String) -> bool def foo(bar); end @@ -689,8 +689,108 @@ def foo(bar); end expect(pin.signatures).to be_one expect(pin.signatures.first.parameters).to be_one expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:arg) expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') - expect(pin.signatures.first.return_type.to_s).to eq('Boolean') + end + + it 'sets optional positional parameters' do + source = Solargraph::Source.load_string(%( + #: (?String) -> bool + def foo(bar = 'default'); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:optarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets rest positional parameters' do + source = Solargraph::Source.load_string(%( + #: (*bar) -> bool + def foo(*bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:restarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('Array') + end + + it 'sets required keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (bar: String) -> bool + def foo(bar:); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets optional keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (?bar: String) -> bool + def foo(bar: 'default'); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwoptarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets rest keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (**bar) -> bool + def foo(**bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwrestarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('Hash{Symbol => Object}') + end + + it 'sets block parameters' do + source = Solargraph::Source.load_string(%( + #: (String) { (Integer) -> void } -> bool + def foo(bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.block.parameters).to be_one + expect(pin.signatures.first.block.parameters.first.return_type.to_s).to eq('Integer') + expect(pin.signatures.first.block.return_type.to_s).to eq('void') + end + + it 'rescues parsing errors' do + source = Solargraph::Source.load_string(%[ + #: (* -> broke + def foo(**bar); end + ]) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect { pin.signatures }.not_to raise_error end end end From e88771f47e0c4c262d68479f56a7e1de78830e7a Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 03:54:24 -0500 Subject: [PATCH 06/53] Linting --- lib/solargraph.rb | 2 +- spec/pin/method_spec.rb | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 73c426c2f..9ad6eac3c 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,7 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' - autoload :RbsTranslator, 'solargraph/rbs_translator' + autoload :RbsTranslator, 'solargraph/rbs_translator' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index d759b372c..09037869b 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -656,17 +656,6 @@ def foo; end expect(pin.return_type.to_s).to eq('Array') end - it 'sets parametrized instance return types' do - source = Solargraph::Source.load_string(%( - #: () -> Array[String] - def foo; end - )) - api_map = Solargraph::ApiMap.new - api_map.map source - pin = api_map.get_path_pins('#foo').first - expect(pin.return_type.to_s).to eq('Array') - end - it 'sets YARD conventional return types' do source = Solargraph::Source.load_string(%( #: () -> bool From 821da530779a0db5a6ebc5ff657ca6b3e7535922 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 03:58:21 -0500 Subject: [PATCH 07/53] Parameters for untyped functions --- lib/solargraph/rbs_translator.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index c1f073452..e24f21c0a 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -39,6 +39,12 @@ def self.to_parameter_pin(param_type, name, decl, closure) # @param parameter_names [Array] # @return [Array] def self.to_parameter_pins method_type, closure, parameter_names = [] + if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) + return [ + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)), + ] + end + arg_num = 0 params = [] method_type.type.required_positionals.each do |param| From 6839560a7db0755e6e1b48f35e4bc26e33b5df15 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 23:56:01 -0500 Subject: [PATCH 08/53] Refactor RbsTranslator.type_to_tag --- lib/solargraph/rbs_translator.rb | 63 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index e24f21c0a..81cf02365 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -78,7 +78,7 @@ def self.to_parameter_pins method_type, closure, parameter_names = [] # @param parameter_names [Array] # @return [Pin::Signature] def self.to_signature method_type, closure, parameter_names = [] - # there may be edge cases here around different signatures + # There may be edge cases here around different signatures # having different type params / orders - we may need to match # this data model and have generics live in signatures to # handle those correctly @@ -125,59 +125,54 @@ class << self # @param type [RBS::Types::Bases::Base] # @return [String] def type_to_tag type - if type.is_a?(RBS::Types::Optional) + case type + when RBS::Types::Optional "#{type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) + when RBS::Types::Bases::Bool 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) + when RBS::Types::Tuple "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) + when RBS::Types::Literal type.literal.inspect - elsif type.is_a?(RBS::Types::Union) + when RBS::Types::Union type.types.map { |t| type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) + when RBS::Types::Record # @todo Better record support 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) + when RBS::Types::Bases::Nil 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) + when RBS::Types::Bases::Void 'void' - elsif type.is_a?(RBS::Types::Variable) + when RBS::Types::Variable "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) + when RBS::Types::Bases::Self, RBS::Types::Bases::Instance 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass + when RBS::Types::Bases::Top + # `Top` is the most super superclass 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) + when RBS::Types::Intersection type.types.map { |member| type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) + when RBS::Types::Proc 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" + when RBS::Types::ClassInstance, RBS::Types::Alias, RBS::Types::Interface + # `Alias` is a top-level type alias, e.g., 'bool' in "type bool = true | false" # @todo ensure these get resolved after processing all aliases # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a + # + # `Interface represents a mix-in module which can be considered a # subtype of a consumer of it + # type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) + when RBS::Types::ClassSingleton # e.g., singleton(String) type_tag(type.name) + when RBS::Types::Bases::Any, RBS::Types::Bases::Bottom + # `Bottom`` is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # @todo define a specific bottom type and use it to + # determine dead code + # + 'undefined' else Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" 'undefined' From 25badf5a5e1f866cfc0281f13737286574856654 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 23:58:58 -0500 Subject: [PATCH 09/53] Trailing comma --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 81cf02365..d8c9b34e8 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -41,7 +41,7 @@ def self.to_parameter_pin(param_type, name, decl, closure) def self.to_parameter_pins method_type, closure, parameter_names = [] if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) return [ - Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)), + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)) ] end From 8c2d26008cf0424e7083c20849d8f7ac94864a75 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:06:35 -0500 Subject: [PATCH 10/53] Ignore type_location for UntypedFunction --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index d8c9b34e8..3c8fb79e6 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -41,7 +41,7 @@ def self.to_parameter_pin(param_type, name, decl, closure) def self.to_parameter_pins method_type, closure, parameter_names = [] if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) return [ - Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)) + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs) ] end From d76e9f606b22e02671d944cf6abf3e3c196ca2c3 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:09:50 -0500 Subject: [PATCH 11/53] Ignore length of Pin::Method --- lib/solargraph/pin/method.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86e851565..e19d415d4 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -4,6 +4,7 @@ module Solargraph module Pin # The base class for method and attribute pins. # + # rubocop:disable Metrics/ClassLength class Method < Callable include Solargraph::Parser::NodeMethods @@ -749,5 +750,6 @@ def inline_rbs .join("\n") end end + # rubocop:enable Metrics/ClassLength end end From 548c67cd207d017a102b5baf6a10935bf66fb81a Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:17:56 -0500 Subject: [PATCH 12/53] Redundant code --- lib/solargraph/pin/method.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e19d415d4..7fc67ff3e 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -189,12 +189,10 @@ def generate_signature parameters, return_type # @return [::Array] def signatures - @signatures ||= begin - if inline_rbs.empty? - signatures_from_yard - else - signatures_from_inline_rbs - end + @signatures ||= if inline_rbs.empty? + signatures_from_yard + else + signatures_from_inline_rbs end end From 37db0e01d67f2ac3cd72ff0ea585900e20fcdf0a Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:43:31 -0500 Subject: [PATCH 13/53] Tags --- lib/solargraph/pin/method.rb | 3 +++ lib/solargraph/rbs_translator.rb | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 7fc67ff3e..ecbb6c7cc 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -725,6 +725,7 @@ def return_type_from_inline_rbs nil end + # @return [Array] def signatures_from_inline_rbs method_type = RBS::Parser.parse_method_type(inline_rbs) [RbsTranslator.to_signature(method_type, self, parameter_names)] @@ -732,6 +733,7 @@ def signatures_from_inline_rbs signatures_from_yard end + # @return [Array] def signatures_from_yard top_type = generate_complex_type result = [] @@ -741,6 +743,7 @@ def signatures_from_yard result end + # @return [String] def inline_rbs comments.lines .select { |line| line.start_with?(': ') } diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 3c8fb79e6..1fa5656a9 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -23,6 +23,7 @@ def self.to_complex_type(type) # @param name [String] # @param decl [Symbol] # @param closure [Pin::Closure] + # @return [Pin::Signature] def self.to_parameter_pin(param_type, name, decl, closure) return_type = if decl == :restarg ComplexType.parse('Array') From 249c6daf1fdc8d35488211992ede8702732ac0af Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:52:52 -0500 Subject: [PATCH 14/53] RbsTranslator roots complex types --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 1fa5656a9..f84cfd8fa 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -16,7 +16,7 @@ module RbsTranslator # @return [ComplexType] def self.to_complex_type(type) tag = type_to_tag(type) - ComplexType.try_parse(tag) + ComplexType.try_parse(tag).force_rooted end # @param param_type [RBS::Types::Function::Param] From 6536a23e035873085eefd62649928991268cf9d1 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:56:59 -0500 Subject: [PATCH 15/53] Redundant force_rooted calls --- lib/solargraph/rbs_translator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index f84cfd8fa..0891c0c32 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -30,7 +30,7 @@ def self.to_parameter_pin(param_type, name, decl, closure) elsif decl == :kwrestarg ComplexType.parse('Hash{Symbol => Object}') else - RbsTranslator.to_complex_type(param_type.type).force_rooted + RbsTranslator.to_complex_type(param_type.type) end Solargraph::Pin::Parameter.new(decl: decl, name: name, closure: closure, return_type: return_type, source: :rbs, type_location: to_sg_location(param_type.location) || closure.type_location) end @@ -100,7 +100,7 @@ def self.to_signature method_type, closure, parameter_names = [] def self.build_unique_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s params = type_args.map do |a| - RbsTranslator.to_complex_type(a).force_rooted + RbsTranslator.to_complex_type(a) end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) @@ -193,7 +193,7 @@ def type_tag(type_name, type_args = []) def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s params = type_args.map { |a| type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted + ComplexType.try_parse(t) end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) From 21fb27cd71006247bb80cf3d5af24ebcf72c9db3 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 01:33:40 -0500 Subject: [PATCH 16/53] Return tag --- lib/solargraph/pin/method.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index ecbb6c7cc..ae7204d9c 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -717,6 +717,7 @@ def concat_example_tags .concat("```\n") end + # @return [ComplexType, nil] def return_type_from_inline_rbs return nil if inline_rbs.empty? method_type = RBS::Parser.parse_method_type(inline_rbs) From 3cdeb781db3cf14345cd7860c00de0e45f1dec60 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 01:42:48 -0500 Subject: [PATCH 17/53] Fixed return tag --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 0891c0c32..a070de1e1 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -23,7 +23,7 @@ def self.to_complex_type(type) # @param name [String] # @param decl [Symbol] # @param closure [Pin::Closure] - # @return [Pin::Signature] + # @return [Pin::Parameter] def self.to_parameter_pin(param_type, name, decl, closure) return_type = if decl == :restarg ComplexType.parse('Array') From 8edcfcd4930c691c34a87b4123be1a37a9aa6444 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 08:52:51 -0400 Subject: [PATCH 18/53] Revert Conversions --- lib/solargraph/rbs_map/conversions.rb | 273 ++++++++++++++++++++++---- 1 file changed, 232 insertions(+), 41 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 4b5191eb7..bda520240 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -184,11 +184,11 @@ def build_type type_name, type_args = [] # @param closure [Pin::Namespace] # @return [void] def convert_self_type_to_pins decl, closure - type = RbsTranslator.build_unique_type(decl.name, decl.args) + type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -275,7 +275,8 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - generic_defaults[param.name.to_s] = RbsTranslator.to_complex_type(param.default_type) + tag = other_type_to_tag param.default_type + generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted end end @@ -288,7 +289,7 @@ def class_decl_to_pin decl name: class_name, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), # @todo some type parameters in core/stdlib have default # values; Solargraph doesn't support that yet as so these # get treated as undefined if not specified @@ -298,11 +299,11 @@ def class_decl_to_pin decl ) pins.push class_pin if decl.super_class - type = RbsTranslator.build_unique_type(decl.super_class.name, decl.super_class.args) + type = build_type(decl.super_class.name, decl.super_class.args) generic_values = type.all_params.map(&:to_s) superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( - type_location: RbsTranslator.to_sg_location(decl.super_class.location), + type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, name: type.rooted_name, # reference pins use rooted names @@ -318,7 +319,7 @@ def class_decl_to_pin decl def interface_decl_to_pin decl class_pin = Solargraph::Pin::Namespace.new( type: :module, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), name: decl.name.relative!.to_s, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, @@ -339,7 +340,7 @@ def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -377,12 +378,15 @@ def create_constant fqns, type, comments, decl, base = nil constant_pin = Solargraph::Pin::Constant.new( name: fqns, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: comments, source: :rbs ) rooted_tag = type.rooted_tags rooted_tag = "#{base}<#{rooted_tag}>" if base + # @todo alt version + # tag = "#{base}<#{tag}>" if base + # rooted_tag = ComplexType.parse(tag).force_rooted.rooted_tags constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin end @@ -409,7 +413,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsTranslator.to_complex_type(decl.type) + tag = other_type_to_tag(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -422,10 +426,10 @@ def global_decl_to_pin decl name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -516,7 +520,7 @@ def method_def_to_pin decl, closure, context pin = Solargraph::Pin::Method.new( name: name, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: decl.comment&.string, scope: final_scope, signatures: [], @@ -539,7 +543,7 @@ def method_def_to_pin decl, closure, context name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), visibility: visibility, scope: final_scope, signatures: [], @@ -553,9 +557,106 @@ def method_def_to_pin decl, closure, context # @param decl [RBS::AST::Members::MethodDefinition] # @param pin [Pin::Method] - # @return [Array] + # @return [void] def method_def_to_sigs decl, pin - decl.overloads.map { |overload| RbsTranslator.to_signature(overload.method_type, pin) } + # @param overload [RBS::AST::Members::MethodDefinition::Overload] + decl.overloads.map do |overload| + type_location = location_decl_to_pin_location(overload.method_type.location) + generics = overload.method_type.type_params.map(&:name).map(&:to_s) + signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) + block = if overload.method_type.block + block_parameters, block_return_type = parts_of_function(overload.method_type.block, pin) + Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, + type_location: type_location, closure: pin) + end + Pin::Signature.new(generics: generics, parameters: signature_parameters, return_type: signature_return_type, block: block, source: :rbs, + type_location: type_location, closure: pin) + end + end + + # @param location [RBS::Location, nil] + # @return [Solargraph::Location, nil] + def location_decl_to_pin_location(location) + return nil if location&.name.nil? + + start_pos = Position.new(location.start_line - 1, location.start_column) + end_pos = Position.new(location.end_line - 1, location.end_column) + range = Range.new(start_pos, end_pos) + Location.new(location.name.to_s, range) + end + + # @param type [RBS::MethodType,RBS::Types::Block] + # @param pin [Pin::Method] + # @return [Array(Array, ComplexType)] + def parts_of_function type, pin + type_location = pin.type_location + if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) + return [ + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], + ComplexType.try_parse(method_type_to_tag(type)).force_rooted + ] + end + + parameters = [] + arg_num = -1 + type.type.required_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + # @sg-ignore RBS generic type understanding issue + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) + end + type.type.optional_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_positionals + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" + inner_rest_positional_type = + ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + rest_positional_type = ComplexType::UniqueType.new('Array', + [], + [inner_rest_positional_type], + rooted: true, parameters_type: :list) + parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, + source: :rbs, type_location: type_location, + return_type: rest_positional_type,) + end + type.type.trailing_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) + end + type.type.required_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + source: :rbs, type_location: type_location) + end + type.type.optional_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_keywords + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, + source: :rbs, type_location: type_location) + end + + rooted_tag = method_type_to_tag(type) + return_type = ComplexType.try_parse(rooted_tag).force_rooted + [parameters, return_type] end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -568,7 +669,7 @@ def attr_reader_to_pin decl, closure, context visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( name: name, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: closure, comments: decl.comment&.string, scope: final_scope, @@ -576,7 +677,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -592,7 +693,7 @@ def attr_writer_to_pin decl, closure, context final_scope = decl.kind == :instance ? :instance : :class name = "#{decl.name}=" visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - type_location = RbsTranslator.to_sg_location(decl.location) + type_location = location_decl_to_pin_location(decl.location) pin = Solargraph::Pin::Method.new( name: name, type_location: type_location, @@ -607,12 +708,12 @@ def attr_writer_to_pin decl, closure, context pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, + return_type: ComplexType.try_parse(other_type_to_tag(decl.type)).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -633,11 +734,11 @@ def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( name: decl.name.to_s, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: decl.comment&.string, source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -651,10 +752,10 @@ def cvar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -668,10 +769,10 @@ def civar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -680,11 +781,11 @@ def civar_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def include_to_pin decl, closure - type = RbsTranslator.build_unique_type(decl.name, decl.args) + type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -699,7 +800,7 @@ def prepend_to_pin decl, closure generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Prepend.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: closure, source: :rbs ) @@ -713,7 +814,7 @@ def extend_to_pin decl, closure generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Extend.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: closure, source: :rbs ) @@ -726,7 +827,7 @@ def alias_to_pin decl, closure final_scope = decl.singleton? ? :class : :instance pins.push Solargraph::Pin::MethodAlias.new( name: decl.new_name.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), original: decl.old_name.to_s, closure: closure, scope: final_scope, @@ -734,16 +835,106 @@ def alias_to_pin decl, closure ) end + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + } + # @param type [RBS::MethodType] # @return [String] def method_type_to_tag type - RbsTranslator.to_complex_type( - if type_aliases.key?(type.type.return_type.to_s) - type_aliases[type.type.return_type.to_s].type - else - type.type.return_type - end - ) + if type_aliases.key?(type.type.return_type.to_s) + other_type_to_tag(type_aliases[type.type.return_type.to_s].type) + else + other_type_to_tag type.type.return_type + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |a| other_type_to_tag(a) }.map do |t| + ComplexType.try_parse(t).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [String] + def type_tag(type_name, type_args = []) + build_type(type_name, type_args).tags + end + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def other_type_to_tag type + if type.is_a?(RBS::Types::Optional) + "#{other_type_to_tag(type.type)}, nil" + elsif type.is_a?(RBS::Types::Bases::Any) + 'undefined' + elsif type.is_a?(RBS::Types::Bases::Bool) + 'Boolean' + elsif type.is_a?(RBS::Types::Tuple) + "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})" + elsif type.is_a?(RBS::Types::Literal) + type.literal.inspect + elsif type.is_a?(RBS::Types::Union) + type.types.map { |t| other_type_to_tag(t) }.join(', ') + elsif type.is_a?(RBS::Types::Record) + # @todo Better record support + 'Hash' + elsif type.is_a?(RBS::Types::Bases::Nil) + 'nil' + elsif type.is_a?(RBS::Types::Bases::Self) + 'self' + elsif type.is_a?(RBS::Types::Bases::Void) + 'void' + elsif type.is_a?(RBS::Types::Variable) + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + elsif type.is_a?(RBS::Types::ClassInstance) #&& !type.args.empty? + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Bases::Instance) + 'self' + elsif type.is_a?(RBS::Types::Bases::Top) + # top is the most super superclass + 'BasicObject' + elsif type.is_a?(RBS::Types::Bases::Bottom) + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + 'undefined' + elsif type.is_a?(RBS::Types::Intersection) + type.types.map { |member| other_type_to_tag(member) }.join(', ') + elsif type.is_a?(RBS::Types::Proc) + 'Proc' + elsif type.is_a?(RBS::Types::Alias) + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Interface) + # represents a mix-in module which can be considered a + # subtype of a consumer of it + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::ClassSingleton) + # e.g., singleton(String) + type_tag(type.name) + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + end end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] @@ -754,11 +945,11 @@ def add_mixins decl, namespace decl.each_mixin do |mixin| # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend - type = RbsTranslator.build_unique_type(mixin.name, mixin.args) + type = build_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) pins.push klass.new( name: mixin.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(mixin.location), + type_location: location_decl_to_pin_location(mixin.location), generic_values: generic_values, closure: namespace, source: :rbs From 6ba97e47eedc1a400ad57715ab978d53edf70c45 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:12:24 -0400 Subject: [PATCH 19/53] Cache command checks workspace for RBS config --- lib/solargraph/shell.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index b4ead7884..f09c0efeb 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -112,12 +112,10 @@ def cache gem, version = nil PinCache.serialize_yard_gem(gemspec, pins) end - workspace = Solargraph::Workspace.new(Dir.pwd) - rbs_map = RbsMap.from_gemspec(gemspec, workspace.rbs_collection_path, workspace.rbs_collection_config_path) + workspace = Solargraph::Workspace.new(Dir.pwd) if File.exist?('rbs_collection.yaml') + rbs_map = RbsMap.from_gemspec(gemspec, workspace&.rbs_collection_path, workspace&.rbs_collection_config_path) if options[:rebuild] || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key) - # cache pins even if result is zero, so we don't retry building pins - pins = rbs_map.pins || [] - PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, pins) + PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, rbs_map.pins) end rescue Gem::MissingSpecError warn "Gem '#{gem}' not found" From b4c59d99cbded33b441dd7de7597c82223189604 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:12:42 -0400 Subject: [PATCH 20/53] Conversions use RbsTranslator.to_complex_type --- lib/solargraph/rbs_map/conversions.rb | 37 +++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index bda520240..8d2a396b1 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -275,8 +275,8 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - tag = other_type_to_tag param.default_type - generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted + complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted + generic_defaults[param.name.to_s] = complex_type end end @@ -429,7 +429,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -593,7 +593,7 @@ def parts_of_function type, pin if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) return [ [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - ComplexType.try_parse(method_type_to_tag(type)).force_rooted + method_type_to_tag(type).force_rooted ] end @@ -603,21 +603,20 @@ def parts_of_function type, pin # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_positionals.each do |param| # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, type_location: type_location, source: :rbs) end if type.type.rest_positionals name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = - ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + inner_rest_positional_type = RbsTranslator.to_complex_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], [inner_rest_positional_type], @@ -636,7 +635,7 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_keywords.each do |orig, param| @@ -644,7 +643,7 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, type_location: type_location, source: :rbs) end @@ -655,7 +654,7 @@ def parts_of_function type, pin end rooted_tag = method_type_to_tag(type) - return_type = ComplexType.try_parse(rooted_tag).force_rooted + return_type = rooted_tag.force_rooted [parameters, return_type] end @@ -677,7 +676,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -708,12 +707,12 @@ def attr_writer_to_pin decl, closure, context pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: ComplexType.try_parse(other_type_to_tag(decl.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -738,7 +737,7 @@ def ivar_to_pin decl, closure comments: decl.comment&.string, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -755,7 +754,7 @@ def cvar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -772,7 +771,7 @@ def civar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -847,9 +846,9 @@ def alias_to_pin decl, closure # @return [String] def method_type_to_tag type if type_aliases.key?(type.type.return_type.to_s) - other_type_to_tag(type_aliases[type.type.return_type.to_s].type) + RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) else - other_type_to_tag type.type.return_type + RbsTranslator.to_complex_type(type.type.return_type) end end From c1038b828245491d4cc522fccc16a449c49d7774 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:31:12 -0400 Subject: [PATCH 21/53] Redundant code --- lib/solargraph/rbs_map/conversions.rb | 75 +-------------------------- 1 file changed, 2 insertions(+), 73 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 8d2a396b1..0e86750c4 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -413,7 +413,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = other_type_to_tag(decl.type) + tag = RbsTranslator.to_complex_type(decl.type).to_s pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -857,9 +857,7 @@ def method_type_to_tag type # @return [ComplexType::UniqueType] def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| other_type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted - end + params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else @@ -867,75 +865,6 @@ def build_type(type_name, type_args = []) end end - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [String] - def type_tag(type_name, type_args = []) - build_type(type_name, type_args).tags - end - - # @param type [RBS::Types::Bases::Base] - # @return [String] - def other_type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{other_type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) - 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) - type.literal.inspect - elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| other_type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) - # @todo Better record support - 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) - 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) - 'void' - elsif type.is_a?(RBS::Types::Variable) - "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) #&& !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) - 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass - 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| other_type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) - 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a - # subtype of a consumer of it - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) - # e.g., singleton(String) - type_tag(type.name) - else - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' - end - end - # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] # @param namespace [Pin::Namespace, nil] # @return [void] From a4b4c8f142dd7ea1fec19564bc18c364ff843eba Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 09:02:19 -0400 Subject: [PATCH 22/53] Redundant code --- lib/solargraph/rbs_map/conversions.rb | 71 ++------------------------- 1 file changed, 4 insertions(+), 67 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 0e86750c4..128b751cf 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -589,73 +589,10 @@ def location_decl_to_pin_location(location) # @param pin [Pin::Method] # @return [Array(Array, ComplexType)] def parts_of_function type, pin - type_location = pin.type_location - if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) - return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - method_type_to_tag(type).force_rooted - ] - end - - parameters = [] - arg_num = -1 - type.type.required_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) - end - type.type.optional_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = RbsTranslator.to_complex_type(type.type.rest_positionals.type) - rest_positional_type = ComplexType::UniqueType.new('Array', - [], - [inner_rest_positional_type], - rooted: true, parameters_type: :list) - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, - source: :rbs, type_location: type_location, - return_type: rest_positional_type,) - end - type.type.trailing_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) - end - type.type.required_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - source: :rbs, type_location: type_location) - end - type.type.optional_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, - source: :rbs, type_location: type_location) - end - - rooted_tag = method_type_to_tag(type) - return_type = rooted_tag.force_rooted - [parameters, return_type] + [ + RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), + method_type_to_tag(type).force_rooted + ] end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] From 745ce367ed1faff3fe707dcbce60865e8804a089 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 09:46:29 -0400 Subject: [PATCH 23/53] Type fix --- lib/solargraph/rbs_map/conversions.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 128b751cf..11552c048 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -591,7 +591,7 @@ def location_decl_to_pin_location(location) def parts_of_function type, pin [ RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), - method_type_to_tag(type).force_rooted + extract_method_type_return_type(type).force_rooted ] end @@ -779,9 +779,13 @@ def alias_to_pin decl, closure 'NilClass' => 'nil' } + # Extract a ComplexType from a MethodType's return type. + # + # This method will convert type aliases to concrete types. + # # @param type [RBS::MethodType] - # @return [String] - def method_type_to_tag type + # @return [ComplexType] + def extract_method_type_return_type type if type_aliases.key?(type.type.return_type.to_s) RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) else From eb3a640caedd0d0ab33d583531e570a1d9ba652c Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 May 2026 11:48:42 -0400 Subject: [PATCH 24/53] Updates for 0.59.0 --- lib/solargraph/rbs_map/conversions.rb | 16 ++++++++-------- spec/rbs_map/core_map_spec.rb | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 11552c048..7fd75cdc7 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -67,7 +67,6 @@ def convert_decl_to_pin decl, closure unless closure.name == '' || decl.name.absolute? Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on interface #{decl.inspect}") end - # STDERR.puts "Skipping interface #{decl.name.relative!}" interface_decl_to_pin decl when RBS::AST::Declarations::TypeAlias # @sg-ignore flow sensitive typing should support case/when @@ -80,7 +79,7 @@ def convert_decl_to_pin decl, closure # @sg-ignore Wrong argument type for Solargraph::Pin::Reference::TypeAlias.new: return_type expected Solargraph::ComplexType, received Solargraph::ComplexType::UniqueType, Solargraph::ComplexType Solargraph::Pin::Reference::TypeAlias.new( # @sg-ignore Unresolved calls to name, type, type_location; return_type type mismatch - name: ComplexType.try_parse(decl.name.to_s).to_s, return_type: other_type_to_type(decl.type).force_rooted, closure: closure, source: :rbs, type_location: location_decl_to_pin_location(decl.location) + name: ComplexType.try_parse(decl.name.to_s).to_s, return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, closure: closure, source: :rbs, type_location: location_decl_to_pin_location(decl.location) ) ) when RBS::AST::Declarations::Module @@ -169,7 +168,8 @@ def build_type type_name, type_args = [] rbs_name = type_name.relative!.to_s base = RBS_TO_CLASS.fetch(rbs_name, rbs_name) - params = type_args.map { |a| other_type_to_type(a) } + params = type_args.map { |a| RbsTranslator.to_complex_type(a) } + # @todo Tuples are in flux # tuples have their own class and are handled in other_type_to_type if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: type_name.absolute?, @@ -413,7 +413,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsTranslator.to_complex_type(decl.type).to_s + tag = RbsTranslator.to_complex_type(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -786,11 +786,11 @@ def alias_to_pin decl, closure # @param type [RBS::MethodType] # @return [ComplexType] def extract_method_type_return_type type - if type_aliases.key?(type.type.return_type.to_s) - RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) - else + # if type_aliases.key?(type.type.return_type.to_s) + # RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) + # else RbsTranslator.to_complex_type(type.type.return_type) - end + # end end # @param type_name [RBS::TypeName] diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index 79878c572..a3769f70a 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -82,7 +82,7 @@ # correctly. It would be better to test RbsMap or RbsMap::Conversions # with an RBS fixture. core_map = described_class.new - pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == '::Enumerable' } + pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == 'Enumerable' } expect(pins.map(&:closure).map(&:namespace)).to include('Enumerator') end From db5de3bba56f9c378fa37a7153774bca54f38295 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 May 2026 14:23:35 -0400 Subject: [PATCH 25/53] Convert RBS implicit nil annotations (#1197) * Convert RBS implicit nil annotations * Specs * Linting * Minor linting * Documentation * Typecheck fix --- lib/solargraph/rbs_map/conversions.rb | 21 ++++++++++++++------- spec/parser/flow_sensitive_typing_spec.rb | 2 +- spec/pin/method_spec.rb | 2 +- spec/source/chain/call_spec.rb | 2 +- spec/source_map/clip_spec.rb | 4 ++-- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 3caf13162..c819136cd 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -552,16 +552,19 @@ def method_def_to_pin decl, closure, context # @param pin [Pin::Method] # @return [void] def method_def_to_sigs decl, pin + # rubocop:disable Style/SafeNavigationChainLength + implicit_nil = decl.overloads.first&.annotations&.map(&:string)&.include?('implicitly-returns-nil') || false + # rubocop:enable Style/SafeNavigationChainLength # @param overload [RBS::AST::Members::MethodDefinition::Overload] decl.overloads.map do |overload| # @sg-ignore Wrong argument type for Solargraph::RbsMap::Conversions#location_decl_to_pin_location: # location expected RBS::Location, nil, received RBS::Location<:type, :type_params>, RBS::AST::Members::Attribute::loc, nil type_location = location_decl_to_pin_location(overload.method_type.location) generics = type_parameter_names(overload.method_type) - signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) + signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin, implicit_nil) rbs_block = overload.method_type.block block = if rbs_block - block_parameters, block_return_type = parts_of_function(rbs_block, pin) + block_parameters, block_return_type = parts_of_function(rbs_block, pin, implicit_nil) Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) @@ -588,14 +591,15 @@ def location_decl_to_pin_location location # @param type [RBS::MethodType, RBS::Types::Block] # @param pin [Pin::Method] + # @param implicit_nil [Boolean] # @return [Array(Array, ComplexType)] - def parts_of_function type, pin + def parts_of_function type, pin, implicit_nil type_location = pin.type_location if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) return [ [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - method_type_to_type(type) + method_type_to_type(type, implicit_nil) ] end @@ -659,7 +663,7 @@ def parts_of_function type, pin source: :rbs, type_location: type_location) end - return_type = method_type_to_type(type) + return_type = method_type_to_type(type, implicit_nil) [parameters, return_type] end @@ -842,9 +846,12 @@ def alias_to_pin decl, closure end # @param type [RBS::MethodType, RBS::Types::Block] + # @param implicit_nil [Boolean] # @return [ComplexType, ComplexType::UniqueType] - def method_type_to_type type - other_type_to_type type.type.return_type + def method_type_to_type type, implicit_nil + tag = other_type_to_type type.type.return_type + return ComplexType.parse("#{tag}, nil") if tag && implicit_nil + tag end # @param type [RBS::Types::Bases::Base,Object] RBS type object. diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index cee6afef1..a277d69f9 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -954,7 +954,7 @@ def a b api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [6, 10]) - expect(clip.infer.to_s).to eq('String') + expect(clip.infer.to_s).to eq('String, nil') clip = api_map.clip_at('test.rb', [7, 17]) expect(clip.infer.to_s).to eq('nil') diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index c109746af..4e51bc03d 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -368,7 +368,7 @@ def bar api_map.map source pin = api_map.get_path_pins('Foo#bar').first type = pin.probe(api_map) - expect(type.to_s).to eq('String') + expect(type.to_s).to eq('String, nil') end it 'infers from multiple-assignment chains' do diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index 122cc2ed7..8bab98d97 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -445,7 +445,7 @@ def foo(params) foo_pin = api_map.source_map('test.rb').pins.find { |p| p.name == 'foo' } chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 8)) type = chain.infer(api_map, foo_pin, api_map.source_map('test.rb').locals) - expect(type.rooted_tags).to eq('::Array, ::Hash{::String => undefined}, ::String, ::Integer') + expect(type.rooted_tags).to eq('::Array, ::Hash{::String => undefined}, ::String, ::Integer, nil') end it 'preserves undefined and underdefined tyypes in resolution' do diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 67b3085b2..6dadad186 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -1129,7 +1129,7 @@ def bar opts = {} clip = map.clip_at('test.rb', Solargraph::Position.new(4, 15)) expect(clip.infer.to_s).to eq('Array, nil') clip = map.clip_at('test.rb', Solargraph::Position.new(5, 15)) - expect(clip.infer.to_s).to eq('String') + expect(clip.infer.to_s).to eq('String, nil') end it 'infers overloads with splats' do @@ -1779,7 +1779,7 @@ def foo; end api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [5, 6]) type = clip.infer - expect(type.to_s).to eq('Enumerable') + expect(type.to_s).to eq('Enumerable, nil') clip = api_map.clip_at('test.rb', [7, 6]) type = clip.infer expect(type.to_s).to eq('String, nil') From 2906e3c9265cc249a5e9483073f75dc61b7e36b0 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 4 Mar 2026 08:52:33 -0500 Subject: [PATCH 26/53] Parse method return types from inline RBS --- lib/solargraph/pin/method.rb | 22 ++++++++++++++++++++-- spec/pin/method_spec.rb | 13 +++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 09d681c67..85c6bf396 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -143,7 +143,7 @@ def symbol_kind end def return_type - @return_type ||= ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) + @return_type ||= generate_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) end # @param parameters [::Array] @@ -451,7 +451,8 @@ def rest_of_stack api_map attr_writer :block, :signature_help, :documentation, :return_type - # @sg-ignore Need to add nil check here + attr_writer :return_type + def dodgy_visibility_source? # as of 2025-03-12, the RBS generator used for # e.g. activesupport did not understand 'private' markings @@ -721,6 +722,23 @@ def concat_example_tags .join("\n") .concat("```\n") end + + def generate_from_inline_rbs + return nil if inline_rbs.empty? + + method_type = RBS::Parser.parse_method_type(inline_rbs) + type_name = method_type.type.return_type.name.to_s + ComplexType.try_parse(method_type.type.return_type.name.to_s) + rescue RBS::ParsingError + nil + end + + def inline_rbs + comments.lines + .select { |line| line.start_with?(': ') } + .map { |line| line[2..].strip } + .join("\n") + end end end end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 4e51bc03d..8594bc5e9 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -632,4 +632,17 @@ def bar expect(pin.overloads).to be_empty end end + + context 'with inline rbs' do + it 'sets return types' do + source = Solargraph::Source.load_string(%( + #: () -> String + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('String') + end + end end From 82b3ff646cc49c59a56bcfb9dc5eaba60a5f1114 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 5 Mar 2026 07:51:38 -0500 Subject: [PATCH 27/53] RbsToComplex module --- lib/solargraph.rb | 1 + lib/solargraph/pin/method.rb | 3 +- lib/solargraph/rbs_map/conversions.rb | 112 ++++++-------------------- lib/solargraph/rbs_to_complex.rb | 102 +++++++++++++++++++++++ spec/pin/method_spec.rb | 35 +++++++- 5 files changed, 161 insertions(+), 92 deletions(-) create mode 100644 lib/solargraph/rbs_to_complex.rb diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 75c454dde..067b067e0 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,6 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' + autoload :RbsToComplex, 'solargraph/rbs_to_complex' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 85c6bf396..dbe656c39 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -727,8 +727,7 @@ def generate_from_inline_rbs return nil if inline_rbs.empty? method_type = RBS::Parser.parse_method_type(inline_rbs) - type_name = method_type.type.return_type.name.to_s - ComplexType.try_parse(method_type.type.return_type.name.to_s) + RbsToComplex.convert(method_type.type.return_type) rescue RBS::ParsingError nil end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index c819136cd..2f2dd1fdc 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -274,7 +274,10 @@ def class_decl_to_pin decl # @type [Hash{String => ComplexType, ComplexType::UniqueType}] generic_defaults = {} decl.type_params.each do |param| - generic_defaults[param.name.to_s] = other_type_to_type param.default_type if param.default_type + if param.default_type + complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted + generic_defaults[param.name.to_s] = complex_type + end end class_name = fqns(decl.name) @@ -406,9 +409,8 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - target_type = other_type_to_type(decl.type) - constant_name = fqns(decl.name) - pins.push create_constant(constant_name, target_type, decl.comment&.string, decl) + tag = RbsTranslator.to_complex_type(decl.type).to_s + pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end # @param decl [RBS::AST::Declarations::Global] @@ -423,7 +425,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -685,7 +687,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -716,13 +718,13 @@ def attr_writer_to_pin decl, closure, context pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: other_type_to_type(decl.type), + return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tags = other_type_to_type(decl.type).rooted_tags - pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tags)) + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -746,7 +748,7 @@ def ivar_to_pin decl, closure comments: decl.comment&.string, source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -763,7 +765,7 @@ def cvar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -780,7 +782,7 @@ def civar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = other_type_to_type(decl.type).rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -854,84 +856,16 @@ def method_type_to_type type, implicit_nil tag end - # @param type [RBS::Types::Bases::Base,Object] RBS type object. - # Note: Generally these extend from RBS::Types::Bases::Base, - # but not all. - # - # @return [ComplexType, ComplexType::UniqueType] - def other_type_to_type type - case type - when RBS::Types::Optional - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.new([other_type_to_type(type.type), - ComplexType::UniqueType::NIL]) - when RBS::Types::Bases::Any - ComplexType::UNDEFINED - when RBS::Types::Bases::Bool - ComplexType::BOOLEAN - when RBS::Types::Tuple - # @sg-ignore flow based typing needs to understand case when class pattern - tuple_types = type.types.map { |t| other_type_to_type(t) } - ComplexType::UniqueType.new('Array', [], tuple_types, rooted: true, parameters_type: :fixed) - when RBS::Types::Literal - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.try_parse(type.literal.inspect).force_rooted - when RBS::Types::Union - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.new(type.types.map { |t| other_type_to_type(t) }) - when RBS::Types::Record - # @todo Better record support - ComplexType::UniqueType.new('Hash', rooted: true) - when RBS::Types::Bases::Nil - ComplexType::NIL - when RBS::Types::Bases::Self - ComplexType::SELF - when RBS::Types::Bases::Void - ComplexType::VOID - when RBS::Types::Variable - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.parse("generic<#{type.name}>").force_rooted - when RBS::Types::ClassInstance # && !type.args.empty? - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name, type.args) - when RBS::Types::Bases::Instance - ComplexType::SELF - when RBS::Types::Bases::Top - # top is the most super superclass - ComplexType::UniqueType.new('BasicObject', rooted: true) - when RBS::Types::Bases::Bottom - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - ComplexType::UNDEFINED - when RBS::Types::Intersection - # @sg-ignore flow based typing needs to understand case when class pattern - ComplexType.new(type.types.map { |member| other_type_to_type(member) }) - when RBS::Types::Proc - ComplexType::UniqueType.new('Proc', rooted: true) - when RBS::Types::Alias - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name, type.args) - when RBS::Types::Interface - # represents a mix-in module which can be considered a - # subtype of a consumer of it - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name, type.args) - when RBS::Types::ClassSingleton - # e.g., singleton(String) - # @sg-ignore flow based typing needs to understand case when class pattern - build_type(type.name) + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else - # RBS doesn't provide a common base class for its type AST nodes - # - # @sg-ignore all types should include location - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - ComplexType::UNDEFINED + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) end end diff --git a/lib/solargraph/rbs_to_complex.rb b/lib/solargraph/rbs_to_complex.rb new file mode 100644 index 000000000..46051bf45 --- /dev/null +++ b/lib/solargraph/rbs_to_complex.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module Solargraph + # Convert RBS::Types to ComplexTypes. + # + module RbsToComplex + # @param type [RBS::Types::Bases::Base] + # @return [ComplexType] + def self.convert type + tag = type_to_tag(type) + ComplexType.try_parse(tag) + end + + class << self + private + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def type_to_tag type + if type.is_a?(RBS::Types::Optional) + "#{type_to_tag(type.type)}, nil" + elsif type.is_a?(RBS::Types::Bases::Any) + 'undefined' + elsif type.is_a?(RBS::Types::Bases::Bool) + 'Boolean' + elsif type.is_a?(RBS::Types::Tuple) + "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" + elsif type.is_a?(RBS::Types::Literal) + type.literal.inspect + elsif type.is_a?(RBS::Types::Union) + type.types.map { |t| type_to_tag(t) }.join(', ') + elsif type.is_a?(RBS::Types::Record) + # @todo Better record support + 'Hash' + elsif type.is_a?(RBS::Types::Bases::Nil) + 'nil' + elsif type.is_a?(RBS::Types::Bases::Self) + 'self' + elsif type.is_a?(RBS::Types::Bases::Void) + 'void' + elsif type.is_a?(RBS::Types::Variable) + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Bases::Instance) + 'self' + elsif type.is_a?(RBS::Types::Bases::Top) + # top is the most super superclass + 'BasicObject' + elsif type.is_a?(RBS::Types::Bases::Bottom) + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + 'undefined' + elsif type.is_a?(RBS::Types::Intersection) + type.types.map { |member| type_to_tag(member) }.join(', ') + elsif type.is_a?(RBS::Types::Proc) + 'Proc' + elsif type.is_a?(RBS::Types::Alias) + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Interface) + # represents a mix-in module which can be considered a + # subtype of a consumer of it + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::ClassSingleton) + # e.g., singleton(String) + type_tag(type.name) + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [String] + def type_tag(type_name, type_args = []) + build_type(type_name, type_args).tags + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RbsMap::Conversions::RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |a| type_to_tag(a) }.map do |t| + ComplexType.try_parse(t).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + end + end +end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 8594bc5e9..9000cfe7a 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -634,7 +634,7 @@ def bar end context 'with inline rbs' do - it 'sets return types' do + it 'sets instance return types' do source = Solargraph::Source.load_string(%( #: () -> String def foo; end @@ -644,5 +644,38 @@ def foo; end pin = api_map.get_path_pins('#foo').first expect(pin.return_type.to_s).to eq('String') end + + it 'sets parameterized instance return types' do + source = Solargraph::Source.load_string(%( + #: () -> Array[String] + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Array') + end + + it 'sets parametrized instance return types' do + source = Solargraph::Source.load_string(%( + #: () -> Array[String] + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Array') + end + + it 'sets YARD conventional return types' do + source = Solargraph::Source.load_string(%( + #: () -> bool + def foo; end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.return_type.to_s).to eq('Boolean') + end end end From 13aa62045f888126e3e93fa3032d793ace178573 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 10:38:36 -0500 Subject: [PATCH 28/53] RbsTranslator --- lib/solargraph.rb | 2 +- lib/solargraph/rbs_to_complex.rb | 102 ---------------- lib/solargraph/rbs_translator.rb | 202 +++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 103 deletions(-) delete mode 100644 lib/solargraph/rbs_to_complex.rb create mode 100644 lib/solargraph/rbs_translator.rb diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 067b067e0..73c426c2f 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,7 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' - autoload :RbsToComplex, 'solargraph/rbs_to_complex' + autoload :RbsTranslator, 'solargraph/rbs_translator' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/lib/solargraph/rbs_to_complex.rb b/lib/solargraph/rbs_to_complex.rb deleted file mode 100644 index 46051bf45..000000000 --- a/lib/solargraph/rbs_to_complex.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -module Solargraph - # Convert RBS::Types to ComplexTypes. - # - module RbsToComplex - # @param type [RBS::Types::Bases::Base] - # @return [ComplexType] - def self.convert type - tag = type_to_tag(type) - ComplexType.try_parse(tag) - end - - class << self - private - - # @param type [RBS::Types::Bases::Base] - # @return [String] - def type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) - 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) - type.literal.inspect - elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) - # @todo Better record support - 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) - 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) - 'void' - elsif type.is_a?(RBS::Types::Variable) - "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) - 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass - 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) - 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a - # subtype of a consumer of it - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) - # e.g., singleton(String) - type_tag(type.name) - else - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' - end - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [String] - def type_tag(type_name, type_args = []) - build_type(type_name, type_args).tags - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [ComplexType::UniqueType] - def build_type(type_name, type_args = []) - base = RbsMap::Conversions::RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted - end - if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) - else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) - end - end - end - end -end diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb new file mode 100644 index 000000000..a17bfd968 --- /dev/null +++ b/lib/solargraph/rbs_translator.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +module Solargraph + # Convert RBS::Types to ComplexTypes. + # + module RbsTranslator + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + } + + # @param type [RBS::Types::Bases::Base] + # @return [ComplexType] + def self.to_complex_type(type) + tag = type_to_tag(type) + ComplexType.try_parse(tag) + end + + def self.to_parameter_pin(param_type, name, closure) + Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: closure, return_type: RbsTranslator.to_complex_type(param_type.type).force_rooted, source: :rbs, type_location: nil) + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def self.build_unique_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map do |a| + RbsTranslator.to_complex_type(a).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + + class << self + private + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def type_to_tag type + if type.is_a?(RBS::Types::Optional) + "#{type_to_tag(type.type)}, nil" + elsif type.is_a?(RBS::Types::Bases::Any) + 'undefined' + elsif type.is_a?(RBS::Types::Bases::Bool) + 'Boolean' + elsif type.is_a?(RBS::Types::Tuple) + "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" + elsif type.is_a?(RBS::Types::Literal) + type.literal.inspect + elsif type.is_a?(RBS::Types::Union) + type.types.map { |t| type_to_tag(t) }.join(', ') + elsif type.is_a?(RBS::Types::Record) + # @todo Better record support + 'Hash' + elsif type.is_a?(RBS::Types::Bases::Nil) + 'nil' + elsif type.is_a?(RBS::Types::Bases::Self) + 'self' + elsif type.is_a?(RBS::Types::Bases::Void) + 'void' + elsif type.is_a?(RBS::Types::Variable) + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Bases::Instance) + 'self' + elsif type.is_a?(RBS::Types::Bases::Top) + # top is the most super superclass + 'BasicObject' + elsif type.is_a?(RBS::Types::Bases::Bottom) + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + 'undefined' + elsif type.is_a?(RBS::Types::Intersection) + type.types.map { |member| type_to_tag(member) }.join(', ') + elsif type.is_a?(RBS::Types::Proc) + 'Proc' + elsif type.is_a?(RBS::Types::Alias) + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Interface) + # represents a mix-in module which can be considered a + # subtype of a consumer of it + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::ClassSingleton) + # e.g., singleton(String) + type_tag(type.name) + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [String] + def type_tag(type_name, type_args = []) + build_type(type_name, type_args).tags + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |a| type_to_tag(a) }.map do |t| + ComplexType.try_parse(t).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + end + + # @param type [RBS::MethodType,RBS::Types::Block] + # @param pin [Pin::Method] + # @return [Array(Array, ComplexType)] + def parts_of_function type, pin + type_location = pin.type_location + if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) + return [ + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], + method_type_to_tag(type).force_rooted + ] + end + + parameters = [] + arg_num = -1 + type.type.required_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + # @sg-ignore RBS generic type understanding issue + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) + end + type.type.optional_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_positionals + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" + inner_rest_positional_type = + RbsTranslator.to_complex_type(type.type.rest_positionals.type) + rest_positional_type = ComplexType::UniqueType.new('Array', + [], + [inner_rest_positional_type], + rooted: true, parameters_type: :list) + parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, + source: :rbs, type_location: type_location, + return_type: rest_positional_type,) + end + type.type.trailing_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) + end + type.type.required_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, + source: :rbs, type_location: type_location) + end + type.type.optional_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_keywords + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, + source: :rbs, type_location: type_location) + end + + return_type = method_type_to_tag(type).force_rooted + [parameters, return_type] + end + end +end From 9c899ac42770993555d7b484f9304d0abe3fb327 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 10:38:50 -0500 Subject: [PATCH 29/53] Generate signatures from inline RBS --- lib/solargraph/pin/method.rb | 27 ++++++++------ lib/solargraph/rbs_map/conversions.rb | 52 +++++++++++---------------- spec/pin/method_spec.rb | 15 ++++++++ 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index dbe656c39..1b31a90bf 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -189,16 +189,22 @@ def generate_signature parameters, return_type # @return [::Array] def signatures @signatures ||= begin - top_type = generate_complex_type - result = [] - result.push generate_signature(parameters, top_type) if top_type.defined? - unless overloads.empty? - result.concat(overloads.map do |meth| - generate_signature(meth.parameters, meth.return_type) - end) + if inline_rbs.empty? + top_type = generate_complex_type + result = [] + result.push generate_signature(parameters, top_type) if top_type.defined? + result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? + result + else + these = [] + # @type [RBS::MethodType] + method_type = RBS::Parser.parse_method_type(inline_rbs) + method_type.type.required_positionals.each_with_index do |pos, idx| + these.push RbsTranslator.to_parameter_pin(pos, parameter_names[idx] || "arg_#{idx}", self) + end + [generate_signature(these, return_type)] end - result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? - result end end @@ -725,9 +731,8 @@ def concat_example_tags def generate_from_inline_rbs return nil if inline_rbs.empty? - method_type = RBS::Parser.parse_method_type(inline_rbs) - RbsToComplex.convert(method_type.type.return_type) + RbsTranslator.to_complex_type(method_type.type.return_type) rescue RBS::ParsingError nil end diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 2f2dd1fdc..c8f7000d6 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -184,8 +184,8 @@ def build_type type_name, type_args = [] # @param closure [Pin::Namespace] # @return [void] def convert_self_type_to_pins decl, closure - type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(decl.name, decl.args) + generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( name: type.name, type_location: location_decl_to_pin_location(decl.location), @@ -275,8 +275,7 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted - generic_defaults[param.name.to_s] = complex_type + generic_defaults[param.name.to_s] = RbsTranslator.to_complex_type(param.default_type) end end @@ -299,8 +298,9 @@ def class_decl_to_pin decl ) pins.push class_pin if decl.super_class - type = build_type(decl.super_class.name, decl.super_class.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(decl.super_class.name, decl.super_class.args) + generic_values = type.all_params.map(&:to_s) + superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, @@ -409,7 +409,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsTranslator.to_complex_type(decl.type).to_s + tag = RbsTranslator.to_complex_type(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -791,8 +791,8 @@ def civar_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def include_to_pin decl, closure - type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(decl.name, decl.args) + generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), @@ -847,26 +847,16 @@ def alias_to_pin decl, closure ) end - # @param type [RBS::MethodType, RBS::Types::Block] - # @param implicit_nil [Boolean] - # @return [ComplexType, ComplexType::UniqueType] - def method_type_to_type type, implicit_nil - tag = other_type_to_type type.type.return_type - return ComplexType.parse("#{tag}, nil") if tag && implicit_nil - tag - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [ComplexType::UniqueType] - def build_type(type_name, type_args = []) - base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } - if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) - else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) - end + # @param type [RBS::MethodType] + # @return [String] + def method_type_to_tag type + RbsTranslator.to_complex_type( + if type_aliases.key?(type.type.return_type.to_s) + type_aliases[type.type.return_type.to_s].type + else + type.type.return_type + end + ) end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] @@ -877,8 +867,8 @@ def add_mixins decl, namespace decl.each_mixin do |mixin| # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend - type = build_type(mixin.name, mixin.args) - generic_values = type.all_params.map(&:rooted_tags) + type = RbsTranslator.build_unique_type(mixin.name, mixin.args) + generic_values = type.all_params.map(&:to_s) pins.push klass.new( name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(mixin.location), diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 9000cfe7a..71dad1a33 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -677,5 +677,20 @@ def foo; end pin = api_map.get_path_pins('#foo').first expect(pin.return_type.to_s).to eq('Boolean') end + + it 'sets signatures' do + source = Solargraph::Source.load_string(%( + #: (String) -> bool + def foo(bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + expect(pin.signatures.first.return_type.to_s).to eq('Boolean') + end end end From b97c02523bc5ddd666133af71cc7e53adb3da8a6 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 23:35:28 -0500 Subject: [PATCH 30/53] Generate all parameters for signatures --- lib/solargraph/pin/method.rb | 35 +++-- lib/solargraph/rbs_map/conversions.rb | 203 ++++++-------------------- lib/solargraph/rbs_translator.rb | 154 +++++++++---------- spec/pin/method_spec.rb | 104 ++++++++++++- 4 files changed, 245 insertions(+), 251 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 1b31a90bf..86e851565 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -143,7 +143,7 @@ def symbol_kind end def return_type - @return_type ||= generate_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) + @return_type ||= return_type_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items)) end # @param parameters [::Array] @@ -190,20 +190,9 @@ def generate_signature parameters, return_type def signatures @signatures ||= begin if inline_rbs.empty? - top_type = generate_complex_type - result = [] - result.push generate_signature(parameters, top_type) if top_type.defined? - result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? - result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? - result + signatures_from_yard else - these = [] - # @type [RBS::MethodType] - method_type = RBS::Parser.parse_method_type(inline_rbs) - method_type.type.required_positionals.each_with_index do |pos, idx| - these.push RbsTranslator.to_parameter_pin(pos, parameter_names[idx] || "arg_#{idx}", self) - end - [generate_signature(these, return_type)] + signatures_from_inline_rbs end end end @@ -729,7 +718,7 @@ def concat_example_tags .concat("```\n") end - def generate_from_inline_rbs + def return_type_from_inline_rbs return nil if inline_rbs.empty? method_type = RBS::Parser.parse_method_type(inline_rbs) RbsTranslator.to_complex_type(method_type.type.return_type) @@ -737,6 +726,22 @@ def generate_from_inline_rbs nil end + def signatures_from_inline_rbs + method_type = RBS::Parser.parse_method_type(inline_rbs) + [RbsTranslator.to_signature(method_type, self, parameter_names)] + rescue RBS::ParsingError + signatures_from_yard + end + + def signatures_from_yard + top_type = generate_complex_type + result = [] + result.push generate_signature(parameters, top_type) if top_type.defined? + result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? + result + end + def inline_rbs comments.lines .select { |line| line.start_with?(': ') } diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index c8f7000d6..4b5191eb7 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -187,8 +187,8 @@ def convert_self_type_to_pins decl, closure type = RbsTranslator.build_unique_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( - name: type.name, - type_location: location_decl_to_pin_location(decl.location), + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -288,7 +288,7 @@ def class_decl_to_pin decl name: class_name, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), # @todo some type parameters in core/stdlib have default # values; Solargraph doesn't support that yet as so these # get treated as undefined if not specified @@ -302,7 +302,7 @@ def class_decl_to_pin decl generic_values = type.all_params.map(&:to_s) superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( - type_location: location_decl_to_pin_location(decl.super_class.location), + type_location: RbsTranslator.to_sg_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, name: type.rooted_name, # reference pins use rooted names @@ -318,8 +318,8 @@ def class_decl_to_pin decl def interface_decl_to_pin decl class_pin = Solargraph::Pin::Namespace.new( type: :module, - type_location: location_decl_to_pin_location(decl.location), - name: fqns(decl.name), + type_location: RbsTranslator.to_sg_location(decl.location), + name: decl.name.relative!.to_s, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -338,8 +338,8 @@ def interface_decl_to_pin decl def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, - name: fqns(decl.name), - type_location: location_decl_to_pin_location(decl.location), + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -377,7 +377,7 @@ def create_constant fqns, type, comments, decl, base = nil constant_pin = Solargraph::Pin::Constant.new( name: fqns, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: comments, source: :rbs ) @@ -422,7 +422,7 @@ def global_decl_to_pin decl name: name, closure: closure, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), source: :rbs ) rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags @@ -516,7 +516,7 @@ def method_def_to_pin decl, closure, context pin = Solargraph::Pin::Method.new( name: name, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: decl.comment&.string, scope: final_scope, signatures: [], @@ -531,142 +531,31 @@ def method_def_to_pin decl, closure, context pin.instance_variable_set(:@return_type, ComplexType::VOID) end end - return unless decl.singleton? - final_scope = :class - name = decl.name.to_s - visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - pin = Solargraph::Pin::Method.new( - name: name, - closure: closure, - comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), - visibility: visibility, - scope: final_scope, - signatures: [], - generics: generics, - source: :rbs - ) - pin.signatures.concat method_def_to_sigs(decl, pin) - pins.push pin + if decl.singleton? + final_scope = :class + name = decl.name.to_s + visibility = calculate_method_visibility(decl, context, closure, final_scope, name) + pin = Solargraph::Pin::Method.new( + name: name, + closure: closure, + comments: decl.comment&.string, + type_location: RbsTranslator.to_sg_location(decl.location), + visibility: visibility, + scope: final_scope, + signatures: [], + generics: generics, + source: :rbs + ) + pin.signatures.concat method_def_to_sigs(decl, pin) + pins.push pin + end end # @param decl [RBS::AST::Members::MethodDefinition] # @param pin [Pin::Method] - # @return [void] + # @return [Array] def method_def_to_sigs decl, pin - # rubocop:disable Style/SafeNavigationChainLength - implicit_nil = decl.overloads.first&.annotations&.map(&:string)&.include?('implicitly-returns-nil') || false - # rubocop:enable Style/SafeNavigationChainLength - # @param overload [RBS::AST::Members::MethodDefinition::Overload] - decl.overloads.map do |overload| - # @sg-ignore Wrong argument type for Solargraph::RbsMap::Conversions#location_decl_to_pin_location: - # location expected RBS::Location, nil, received RBS::Location<:type, :type_params>, RBS::AST::Members::Attribute::loc, nil - type_location = location_decl_to_pin_location(overload.method_type.location) - generics = type_parameter_names(overload.method_type) - signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin, implicit_nil) - rbs_block = overload.method_type.block - block = if rbs_block - block_parameters, block_return_type = parts_of_function(rbs_block, pin, implicit_nil) - Pin::Signature.new(generics: generics, parameters: block_parameters, - return_type: block_return_type, source: :rbs, - type_location: type_location, closure: pin) - end - Pin::Signature.new(generics: generics, parameters: signature_parameters, - return_type: signature_return_type, block: block, source: :rbs, - type_location: type_location, closure: pin) - end - end - - # @param location [RBS::Location, nil] - # @return [Solargraph::Location, nil] - def location_decl_to_pin_location location - return nil if location&.name.nil? - - # @sg-ignore flow sensitive typing should handle return nil if location&.name.nil? - start_pos = Position.new(location.start_line - 1, location.start_column) - # @sg-ignore flow sensitive typing should handle return nil if location&.name.nil? - end_pos = Position.new(location.end_line - 1, location.end_column) - range = Range.new(start_pos, end_pos) - # @sg-ignore flow sensitve typing should handle return nil if location&.name.nil? - Location.new(location.name.to_s, range) - end - - # @param type [RBS::MethodType, RBS::Types::Block] - # @param pin [Pin::Method] - # @param implicit_nil [Boolean] - # @return [Array(Array, ComplexType)] - def parts_of_function type, pin, implicit_nil - type_location = pin.type_location - if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) - return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, - type_location: type_location)], - method_type_to_type(type, implicit_nil) - ] - end - - parameters = [] - arg_num = -1 - type.type.required_positionals.each do |param| - # @sg-ignore Unresolved call to name - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - source: :rbs, type_location: type_location) - end - type.type.optional_positionals.each do |param| - # @sg-ignore Unresolved call to name - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - type_location: type_location, - source: :rbs) - end - if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) - rest_positional_type = ComplexType::UniqueType.new('Array', - [], - [inner_rest_positional_type], - rooted: true, parameters_type: :list) - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, - source: :rbs, type_location: type_location, - return_type: rest_positional_type) - end - type.type.trailing_positionals.each do |param| - # @sg-ignore Unresolved call to name - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, - type_location: type_location) - end - type.type.required_keywords.each do |orig, param| - # @sg-ignore Unresolved call to to_s - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - source: :rbs, type_location: type_location) - end - type.type.optional_keywords.each do |orig, param| - # @sg-ignore Unresolved call to to_s - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: other_type_to_type(param.type), - type_location: type_location, - source: :rbs) - end - if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, - name: type.type.rest_keywords.name.to_s, closure: pin, - source: :rbs, type_location: type_location) - end - - return_type = method_type_to_type(type, implicit_nil) - [parameters, return_type] + decl.overloads.map { |overload| RbsTranslator.to_signature(overload.method_type, pin) } end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -679,7 +568,7 @@ def attr_reader_to_pin decl, closure, context visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( name: name, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, comments: decl.comment&.string, scope: final_scope, @@ -703,7 +592,7 @@ def attr_writer_to_pin decl, closure, context final_scope = decl.kind == :instance ? :instance : :class name = "#{decl.name}=" visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - type_location = location_decl_to_pin_location(decl.location) + type_location = RbsTranslator.to_sg_location(decl.location) pin = Solargraph::Pin::Method.new( name: name, type_location: type_location, @@ -744,7 +633,7 @@ def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( name: decl.name.to_s, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: decl.comment&.string, source: :rbs ) @@ -762,7 +651,7 @@ def cvar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), source: :rbs ) rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags @@ -779,7 +668,7 @@ def civar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), source: :rbs ) rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags @@ -794,8 +683,8 @@ def include_to_pin decl, closure type = RbsTranslator.build_unique_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(decl.location), + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -809,9 +698,8 @@ def prepend_to_pin decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Prepend.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(decl.location), - generic_values: generic_values, + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, source: :rbs ) @@ -824,9 +712,8 @@ def extend_to_pin decl, closure type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Extend.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(decl.location), - generic_values: generic_values, + name: decl.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, source: :rbs ) @@ -839,7 +726,7 @@ def alias_to_pin decl, closure final_scope = decl.singleton? ? :class : :instance pins.push Solargraph::Pin::MethodAlias.new( name: decl.new_name.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), original: decl.old_name.to_s, closure: closure, scope: final_scope, @@ -870,8 +757,8 @@ def add_mixins decl, namespace type = RbsTranslator.build_unique_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) pins.push klass.new( - name: type.rooted_name, # reference pins use rooted names - type_location: location_decl_to_pin_location(mixin.location), + name: mixin.name.relative!.to_s, + type_location: RbsTranslator.to_sg_location(mixin.location), generic_values: generic_values, closure: namespace, source: :rbs diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index a17bfd968..c1f073452 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Solargraph - # Convert RBS::Types to ComplexTypes. + # Convert RBS types to complex types and pins. # module RbsTranslator RBS_TO_YARD_TYPE = { @@ -19,8 +19,72 @@ def self.to_complex_type(type) ComplexType.try_parse(tag) end - def self.to_parameter_pin(param_type, name, closure) - Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: closure, return_type: RbsTranslator.to_complex_type(param_type.type).force_rooted, source: :rbs, type_location: nil) + # @param param_type [RBS::Types::Function::Param] + # @param name [String] + # @param decl [Symbol] + # @param closure [Pin::Closure] + def self.to_parameter_pin(param_type, name, decl, closure) + return_type = if decl == :restarg + ComplexType.parse('Array') + elsif decl == :kwrestarg + ComplexType.parse('Hash{Symbol => Object}') + else + RbsTranslator.to_complex_type(param_type.type).force_rooted + end + Solargraph::Pin::Parameter.new(decl: decl, name: name, closure: closure, return_type: return_type, source: :rbs, type_location: to_sg_location(param_type.location) || closure.type_location) + end + + # @param method_type [RBS::MethodType] + # @param closure [Pin::Closure] + # @param parameter_names [Array] + # @return [Array] + def self.to_parameter_pins method_type, closure, parameter_names = [] + arg_num = 0 + params = [] + method_type.type.required_positionals.each do |param| + params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :arg, closure) + arg_num += 1 + end + method_type.type.optional_positionals.each do |param| + params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :optarg, closure) + arg_num += 1 + end + if method_type.type.rest_positionals + params.push RbsTranslator.to_parameter_pin(method_type.type.rest_positionals, method_type.type.rest_positionals.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :restarg, closure) + arg_num += 1 + end + method_type.type.required_keywords.each do |param| + params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwarg, closure) + arg_num += 1 + end + method_type.type.optional_keywords.each do |param| + params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwoptarg, closure) + arg_num += 1 + end + if method_type.type.rest_keywords + params.push RbsTranslator.to_parameter_pin(method_type.type.rest_keywords, method_type.type.rest_keywords.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :kwrestarg, closure) + end + params + end + + # @param method_type [RBS::MethodType] + # @param closure [Pin::Closure] + # @param parameter_names [Array] + # @return [Pin::Signature] + def self.to_signature method_type, closure, parameter_names = [] + # there may be edge cases here around different signatures + # having different type params / orders - we may need to match + # this data model and have generics live in signatures to + # handle those correctly + generics = method_type.type_params.map(&:name).map(&:to_s).uniq + parameters = to_parameter_pins(method_type, closure, parameter_names) + return_type = to_complex_type(method_type.type.return_type) + block = if method_type.block + block_parameters = to_parameter_pins(method_type.block, closure) + block_return_type = to_complex_type(method_type.block.type.return_type) + Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: closure.location, closure: closure) + end + Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, source: :rbs, type_location: closure.location, closure: closure) end # @param type_name [RBS::TypeName] @@ -38,6 +102,17 @@ def self.build_unique_type(type_name, type_args = []) end end + # @param location [RBS::Location, nil] + # @return [Solargraph::Location, nil] + def self.to_sg_location(location) + return nil if location&.name.nil? + + start_pos = Position.new(location.start_line - 1, location.start_column) + end_pos = Position.new(location.end_line - 1, location.end_column) + range = Range.new(start_pos, end_pos) + Location.new(location.name.to_s, range) + end + class << self private @@ -125,78 +200,5 @@ def build_type(type_name, type_args = []) end end end - - # @param type [RBS::MethodType,RBS::Types::Block] - # @param pin [Pin::Method] - # @return [Array(Array, ComplexType)] - def parts_of_function type, pin - type_location = pin.type_location - if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) - return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - method_type_to_tag(type).force_rooted - ] - end - - parameters = [] - arg_num = -1 - type.type.required_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) - end - type.type.optional_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = - RbsTranslator.to_complex_type(type.type.rest_positionals.type) - rest_positional_type = ComplexType::UniqueType.new('Array', - [], - [inner_rest_positional_type], - rooted: true, parameters_type: :list) - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, - source: :rbs, type_location: type_location, - return_type: rest_positional_type,) - end - type.type.trailing_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) - end - type.type.required_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - source: :rbs, type_location: type_location) - end - type.type.optional_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, - source: :rbs, type_location: type_location) - end - - return_type = method_type_to_tag(type).force_rooted - [parameters, return_type] - end end end diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 71dad1a33..e06b919f4 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -678,7 +678,7 @@ def foo; end expect(pin.return_type.to_s).to eq('Boolean') end - it 'sets signatures' do + it 'sets required positional parameters' do source = Solargraph::Source.load_string(%( #: (String) -> bool def foo(bar); end @@ -689,8 +689,108 @@ def foo(bar); end expect(pin.signatures).to be_one expect(pin.signatures.first.parameters).to be_one expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:arg) expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') - expect(pin.signatures.first.return_type.to_s).to eq('Boolean') + end + + it 'sets optional positional parameters' do + source = Solargraph::Source.load_string(%( + #: (?String) -> bool + def foo(bar = 'default'); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:optarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets rest positional parameters' do + source = Solargraph::Source.load_string(%( + #: (*bar) -> bool + def foo(*bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:restarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('Array') + end + + it 'sets required keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (bar: String) -> bool + def foo(bar:); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets optional keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (?bar: String) -> bool + def foo(bar: 'default'); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwoptarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('String') + end + + it 'sets rest keyword parameters' do + source = Solargraph::Source.load_string(%( + #: (**bar) -> bool + def foo(**bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.parameters).to be_one + expect(pin.signatures.first.parameters.first.name).to eq('bar') + expect(pin.signatures.first.parameters.first.decl).to eq(:kwrestarg) + expect(pin.signatures.first.parameters.first.return_type.to_s).to eq('Hash{Symbol => Object}') + end + + it 'sets block parameters' do + source = Solargraph::Source.load_string(%( + #: (String) { (Integer) -> void } -> bool + def foo(bar); end + )) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect(pin.signatures).to be_one + expect(pin.signatures.first.block.parameters).to be_one + expect(pin.signatures.first.block.parameters.first.return_type.to_s).to eq('Integer') + expect(pin.signatures.first.block.return_type.to_s).to eq('void') + end + + it 'rescues parsing errors' do + source = Solargraph::Source.load_string(%[ + #: (* -> broke + def foo(**bar); end + ]) + api_map = Solargraph::ApiMap.new + api_map.map source + pin = api_map.get_path_pins('#foo').first + expect { pin.signatures }.not_to raise_error end end end From fb3089dbd301378f172baef7ed60ae642cd53611 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 03:54:24 -0500 Subject: [PATCH 31/53] Linting --- lib/solargraph.rb | 2 +- spec/pin/method_spec.rb | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 73c426c2f..9ad6eac3c 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -49,7 +49,7 @@ class InvalidRubocopVersionError < RuntimeError; end autoload :RbsMap, 'solargraph/rbs_map' autoload :GemPins, 'solargraph/gem_pins' autoload :PinCache, 'solargraph/pin_cache' - autoload :RbsTranslator, 'solargraph/rbs_translator' + autoload :RbsTranslator, 'solargraph/rbs_translator' dir = File.dirname(__FILE__) VIEWS_PATH = File.join(dir, 'solargraph', 'views') diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index e06b919f4..5fe116cd0 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -656,17 +656,6 @@ def foo; end expect(pin.return_type.to_s).to eq('Array') end - it 'sets parametrized instance return types' do - source = Solargraph::Source.load_string(%( - #: () -> Array[String] - def foo; end - )) - api_map = Solargraph::ApiMap.new - api_map.map source - pin = api_map.get_path_pins('#foo').first - expect(pin.return_type.to_s).to eq('Array') - end - it 'sets YARD conventional return types' do source = Solargraph::Source.load_string(%( #: () -> bool From da43a95219ad5c633b7a52876aea96bad93ddbc5 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 03:58:21 -0500 Subject: [PATCH 32/53] Parameters for untyped functions --- lib/solargraph/rbs_translator.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index c1f073452..e24f21c0a 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -39,6 +39,12 @@ def self.to_parameter_pin(param_type, name, decl, closure) # @param parameter_names [Array] # @return [Array] def self.to_parameter_pins method_type, closure, parameter_names = [] + if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) + return [ + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)), + ] + end + arg_num = 0 params = [] method_type.type.required_positionals.each do |param| From a7ba70044086977d5ab3eaad8c42537c6377f892 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 23:56:01 -0500 Subject: [PATCH 33/53] Refactor RbsTranslator.type_to_tag --- lib/solargraph/rbs_translator.rb | 63 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index e24f21c0a..81cf02365 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -78,7 +78,7 @@ def self.to_parameter_pins method_type, closure, parameter_names = [] # @param parameter_names [Array] # @return [Pin::Signature] def self.to_signature method_type, closure, parameter_names = [] - # there may be edge cases here around different signatures + # There may be edge cases here around different signatures # having different type params / orders - we may need to match # this data model and have generics live in signatures to # handle those correctly @@ -125,59 +125,54 @@ class << self # @param type [RBS::Types::Bases::Base] # @return [String] def type_to_tag type - if type.is_a?(RBS::Types::Optional) + case type + when RBS::Types::Optional "#{type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) + when RBS::Types::Bases::Bool 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) + when RBS::Types::Tuple "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) + when RBS::Types::Literal type.literal.inspect - elsif type.is_a?(RBS::Types::Union) + when RBS::Types::Union type.types.map { |t| type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) + when RBS::Types::Record # @todo Better record support 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) + when RBS::Types::Bases::Nil 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) + when RBS::Types::Bases::Void 'void' - elsif type.is_a?(RBS::Types::Variable) + when RBS::Types::Variable "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) # && !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) + when RBS::Types::Bases::Self, RBS::Types::Bases::Instance 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass + when RBS::Types::Bases::Top + # `Top` is the most super superclass 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) + when RBS::Types::Intersection type.types.map { |member| type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) + when RBS::Types::Proc 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" + when RBS::Types::ClassInstance, RBS::Types::Alias, RBS::Types::Interface + # `Alias` is a top-level type alias, e.g., 'bool' in "type bool = true | false" # @todo ensure these get resolved after processing all aliases # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a + # + # `Interface represents a mix-in module which can be considered a # subtype of a consumer of it + # type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) + when RBS::Types::ClassSingleton # e.g., singleton(String) type_tag(type.name) + when RBS::Types::Bases::Any, RBS::Types::Bases::Bottom + # `Bottom`` is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # @todo define a specific bottom type and use it to + # determine dead code + # + 'undefined' else Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" 'undefined' From ba7a7b7ebba224b79bd1d82593dd050904fece1a Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 23:58:58 -0500 Subject: [PATCH 34/53] Trailing comma --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 81cf02365..d8c9b34e8 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -41,7 +41,7 @@ def self.to_parameter_pin(param_type, name, decl, closure) def self.to_parameter_pins method_type, closure, parameter_names = [] if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) return [ - Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)), + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)) ] end From d62095b0891973b640346d8399907330728526cd Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:06:35 -0500 Subject: [PATCH 35/53] Ignore type_location for UntypedFunction --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index d8c9b34e8..3c8fb79e6 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -41,7 +41,7 @@ def self.to_parameter_pin(param_type, name, decl, closure) def self.to_parameter_pins method_type, closure, parameter_names = [] if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction) return [ - Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs, type_location: to_sg_location(method_type.location)) + Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs) ] end From 43db78ae2e63e099d4dbf35803731ab0947d45e9 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:09:50 -0500 Subject: [PATCH 36/53] Ignore length of Pin::Method --- lib/solargraph/pin/method.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86e851565..e19d415d4 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -4,6 +4,7 @@ module Solargraph module Pin # The base class for method and attribute pins. # + # rubocop:disable Metrics/ClassLength class Method < Callable include Solargraph::Parser::NodeMethods @@ -749,5 +750,6 @@ def inline_rbs .join("\n") end end + # rubocop:enable Metrics/ClassLength end end From 9ea9fa71160e7755c40f46b8e6acc6a95a3c7e9f Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:17:56 -0500 Subject: [PATCH 37/53] Redundant code --- lib/solargraph/pin/method.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e19d415d4..7fc67ff3e 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -189,12 +189,10 @@ def generate_signature parameters, return_type # @return [::Array] def signatures - @signatures ||= begin - if inline_rbs.empty? - signatures_from_yard - else - signatures_from_inline_rbs - end + @signatures ||= if inline_rbs.empty? + signatures_from_yard + else + signatures_from_inline_rbs end end From e2f093ff2c75048bf10f15a0214a553cbcc2445e Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:43:31 -0500 Subject: [PATCH 38/53] Tags --- lib/solargraph/pin/method.rb | 3 +++ lib/solargraph/rbs_translator.rb | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 7fc67ff3e..ecbb6c7cc 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -725,6 +725,7 @@ def return_type_from_inline_rbs nil end + # @return [Array] def signatures_from_inline_rbs method_type = RBS::Parser.parse_method_type(inline_rbs) [RbsTranslator.to_signature(method_type, self, parameter_names)] @@ -732,6 +733,7 @@ def signatures_from_inline_rbs signatures_from_yard end + # @return [Array] def signatures_from_yard top_type = generate_complex_type result = [] @@ -741,6 +743,7 @@ def signatures_from_yard result end + # @return [String] def inline_rbs comments.lines .select { |line| line.start_with?(': ') } diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 3c8fb79e6..1fa5656a9 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -23,6 +23,7 @@ def self.to_complex_type(type) # @param name [String] # @param decl [Symbol] # @param closure [Pin::Closure] + # @return [Pin::Signature] def self.to_parameter_pin(param_type, name, decl, closure) return_type = if decl == :restarg ComplexType.parse('Array') From 015c10e0717152dc63316ab987f00970d3743da8 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:52:52 -0500 Subject: [PATCH 39/53] RbsTranslator roots complex types --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 1fa5656a9..f84cfd8fa 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -16,7 +16,7 @@ module RbsTranslator # @return [ComplexType] def self.to_complex_type(type) tag = type_to_tag(type) - ComplexType.try_parse(tag) + ComplexType.try_parse(tag).force_rooted end # @param param_type [RBS::Types::Function::Param] From 245516792ac369602cf47c393db42fb57540be4f Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:56:59 -0500 Subject: [PATCH 40/53] Redundant force_rooted calls --- lib/solargraph/rbs_translator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index f84cfd8fa..0891c0c32 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -30,7 +30,7 @@ def self.to_parameter_pin(param_type, name, decl, closure) elsif decl == :kwrestarg ComplexType.parse('Hash{Symbol => Object}') else - RbsTranslator.to_complex_type(param_type.type).force_rooted + RbsTranslator.to_complex_type(param_type.type) end Solargraph::Pin::Parameter.new(decl: decl, name: name, closure: closure, return_type: return_type, source: :rbs, type_location: to_sg_location(param_type.location) || closure.type_location) end @@ -100,7 +100,7 @@ def self.to_signature method_type, closure, parameter_names = [] def self.build_unique_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s params = type_args.map do |a| - RbsTranslator.to_complex_type(a).force_rooted + RbsTranslator.to_complex_type(a) end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) @@ -193,7 +193,7 @@ def type_tag(type_name, type_args = []) def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s params = type_args.map { |a| type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted + ComplexType.try_parse(t) end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) From ee4b7ed2d424d584a1418e6639808a2a6f3adbf4 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 01:33:40 -0500 Subject: [PATCH 41/53] Return tag --- lib/solargraph/pin/method.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index ecbb6c7cc..ae7204d9c 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -717,6 +717,7 @@ def concat_example_tags .concat("```\n") end + # @return [ComplexType, nil] def return_type_from_inline_rbs return nil if inline_rbs.empty? method_type = RBS::Parser.parse_method_type(inline_rbs) From f15f6084a76f44c202bfe0384846b1edc4736c89 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 01:42:48 -0500 Subject: [PATCH 42/53] Fixed return tag --- lib/solargraph/rbs_translator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solargraph/rbs_translator.rb b/lib/solargraph/rbs_translator.rb index 0891c0c32..a070de1e1 100644 --- a/lib/solargraph/rbs_translator.rb +++ b/lib/solargraph/rbs_translator.rb @@ -23,7 +23,7 @@ def self.to_complex_type(type) # @param name [String] # @param decl [Symbol] # @param closure [Pin::Closure] - # @return [Pin::Signature] + # @return [Pin::Parameter] def self.to_parameter_pin(param_type, name, decl, closure) return_type = if decl == :restarg ComplexType.parse('Array') From 5835864ed3335eaf49afc18d757e861268f24d09 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 08:52:51 -0400 Subject: [PATCH 43/53] Revert Conversions --- lib/solargraph/rbs_map/conversions.rb | 273 ++++++++++++++++++++++---- 1 file changed, 232 insertions(+), 41 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 4b5191eb7..bda520240 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -184,11 +184,11 @@ def build_type type_name, type_args = [] # @param closure [Pin::Namespace] # @return [void] def convert_self_type_to_pins decl, closure - type = RbsTranslator.build_unique_type(decl.name, decl.args) + type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -275,7 +275,8 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - generic_defaults[param.name.to_s] = RbsTranslator.to_complex_type(param.default_type) + tag = other_type_to_tag param.default_type + generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted end end @@ -288,7 +289,7 @@ def class_decl_to_pin decl name: class_name, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), # @todo some type parameters in core/stdlib have default # values; Solargraph doesn't support that yet as so these # get treated as undefined if not specified @@ -298,11 +299,11 @@ def class_decl_to_pin decl ) pins.push class_pin if decl.super_class - type = RbsTranslator.build_unique_type(decl.super_class.name, decl.super_class.args) + type = build_type(decl.super_class.name, decl.super_class.args) generic_values = type.all_params.map(&:to_s) superclass_name = decl.super_class.name.to_s pins.push Solargraph::Pin::Reference::Superclass.new( - type_location: RbsTranslator.to_sg_location(decl.super_class.location), + type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, name: type.rooted_name, # reference pins use rooted names @@ -318,7 +319,7 @@ def class_decl_to_pin decl def interface_decl_to_pin decl class_pin = Solargraph::Pin::Namespace.new( type: :module, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), name: decl.name.relative!.to_s, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, @@ -339,7 +340,7 @@ def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: type_parameter_names(decl), @@ -377,12 +378,15 @@ def create_constant fqns, type, comments, decl, base = nil constant_pin = Solargraph::Pin::Constant.new( name: fqns, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: comments, source: :rbs ) rooted_tag = type.rooted_tags rooted_tag = "#{base}<#{rooted_tag}>" if base + # @todo alt version + # tag = "#{base}<#{tag}>" if base + # rooted_tag = ComplexType.parse(tag).force_rooted.rooted_tags constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin end @@ -409,7 +413,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsTranslator.to_complex_type(decl.type) + tag = other_type_to_tag(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -422,10 +426,10 @@ def global_decl_to_pin decl name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -516,7 +520,7 @@ def method_def_to_pin decl, closure, context pin = Solargraph::Pin::Method.new( name: name, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: decl.comment&.string, scope: final_scope, signatures: [], @@ -539,7 +543,7 @@ def method_def_to_pin decl, closure, context name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), visibility: visibility, scope: final_scope, signatures: [], @@ -553,9 +557,106 @@ def method_def_to_pin decl, closure, context # @param decl [RBS::AST::Members::MethodDefinition] # @param pin [Pin::Method] - # @return [Array] + # @return [void] def method_def_to_sigs decl, pin - decl.overloads.map { |overload| RbsTranslator.to_signature(overload.method_type, pin) } + # @param overload [RBS::AST::Members::MethodDefinition::Overload] + decl.overloads.map do |overload| + type_location = location_decl_to_pin_location(overload.method_type.location) + generics = overload.method_type.type_params.map(&:name).map(&:to_s) + signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) + block = if overload.method_type.block + block_parameters, block_return_type = parts_of_function(overload.method_type.block, pin) + Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, + type_location: type_location, closure: pin) + end + Pin::Signature.new(generics: generics, parameters: signature_parameters, return_type: signature_return_type, block: block, source: :rbs, + type_location: type_location, closure: pin) + end + end + + # @param location [RBS::Location, nil] + # @return [Solargraph::Location, nil] + def location_decl_to_pin_location(location) + return nil if location&.name.nil? + + start_pos = Position.new(location.start_line - 1, location.start_column) + end_pos = Position.new(location.end_line - 1, location.end_column) + range = Range.new(start_pos, end_pos) + Location.new(location.name.to_s, range) + end + + # @param type [RBS::MethodType,RBS::Types::Block] + # @param pin [Pin::Method] + # @return [Array(Array, ComplexType)] + def parts_of_function type, pin + type_location = pin.type_location + if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) + return [ + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], + ComplexType.try_parse(method_type_to_tag(type)).force_rooted + ] + end + + parameters = [] + arg_num = -1 + type.type.required_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + # @sg-ignore RBS generic type understanding issue + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) + end + type.type.optional_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_positionals + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" + inner_rest_positional_type = + ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + rest_positional_type = ComplexType::UniqueType.new('Array', + [], + [inner_rest_positional_type], + rooted: true, parameters_type: :list) + parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, + source: :rbs, type_location: type_location, + return_type: rest_positional_type,) + end + type.type.trailing_positionals.each do |param| + # @sg-ignore RBS generic type understanding issue + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) + end + type.type.required_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + source: :rbs, type_location: type_location) + end + type.type.optional_keywords.each do |orig, param| + # @sg-ignore RBS generic type understanding issue + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + type_location: type_location, + source: :rbs) + end + if type.type.rest_keywords + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, + source: :rbs, type_location: type_location) + end + + rooted_tag = method_type_to_tag(type) + return_type = ComplexType.try_parse(rooted_tag).force_rooted + [parameters, return_type] end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -568,7 +669,7 @@ def attr_reader_to_pin decl, closure, context visibility = calculate_method_visibility(decl, context, closure, final_scope, name) pin = Solargraph::Pin::Method.new( name: name, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: closure, comments: decl.comment&.string, scope: final_scope, @@ -576,7 +677,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -592,7 +693,7 @@ def attr_writer_to_pin decl, closure, context final_scope = decl.kind == :instance ? :instance : :class name = "#{decl.name}=" visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - type_location = RbsTranslator.to_sg_location(decl.location) + type_location = location_decl_to_pin_location(decl.location) pin = Solargraph::Pin::Method.new( name: name, type_location: type_location, @@ -607,12 +708,12 @@ def attr_writer_to_pin decl, closure, context pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, + return_type: ComplexType.try_parse(other_type_to_tag(decl.type)).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -633,11 +734,11 @@ def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( name: decl.name.to_s, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: decl.comment&.string, source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -651,10 +752,10 @@ def cvar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -668,10 +769,10 @@ def civar_to_pin decl, closure name: name, closure: closure, comments: decl.comment&.string, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags + rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -680,11 +781,11 @@ def civar_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def include_to_pin decl, closure - type = RbsTranslator.build_unique_type(decl.name, decl.args) + type = build_type(decl.name, decl.args) generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -699,7 +800,7 @@ def prepend_to_pin decl, closure generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Prepend.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: closure, source: :rbs ) @@ -713,7 +814,7 @@ def extend_to_pin decl, closure generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Extend.new( name: decl.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), closure: closure, source: :rbs ) @@ -726,7 +827,7 @@ def alias_to_pin decl, closure final_scope = decl.singleton? ? :class : :instance pins.push Solargraph::Pin::MethodAlias.new( name: decl.new_name.to_s, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), original: decl.old_name.to_s, closure: closure, scope: final_scope, @@ -734,16 +835,106 @@ def alias_to_pin decl, closure ) end + RBS_TO_YARD_TYPE = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer', + 'untyped' => '', + 'NilClass' => 'nil' + } + # @param type [RBS::MethodType] # @return [String] def method_type_to_tag type - RbsTranslator.to_complex_type( - if type_aliases.key?(type.type.return_type.to_s) - type_aliases[type.type.return_type.to_s].type - else - type.type.return_type - end - ) + if type_aliases.key?(type.type.return_type.to_s) + other_type_to_tag(type_aliases[type.type.return_type.to_s].type) + else + other_type_to_tag type.type.return_type + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type(type_name, type_args = []) + base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s + params = type_args.map { |a| other_type_to_tag(a) }.map do |t| + ComplexType.try_parse(t).force_rooted + end + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + end + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [String] + def type_tag(type_name, type_args = []) + build_type(type_name, type_args).tags + end + + # @param type [RBS::Types::Bases::Base] + # @return [String] + def other_type_to_tag type + if type.is_a?(RBS::Types::Optional) + "#{other_type_to_tag(type.type)}, nil" + elsif type.is_a?(RBS::Types::Bases::Any) + 'undefined' + elsif type.is_a?(RBS::Types::Bases::Bool) + 'Boolean' + elsif type.is_a?(RBS::Types::Tuple) + "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})" + elsif type.is_a?(RBS::Types::Literal) + type.literal.inspect + elsif type.is_a?(RBS::Types::Union) + type.types.map { |t| other_type_to_tag(t) }.join(', ') + elsif type.is_a?(RBS::Types::Record) + # @todo Better record support + 'Hash' + elsif type.is_a?(RBS::Types::Bases::Nil) + 'nil' + elsif type.is_a?(RBS::Types::Bases::Self) + 'self' + elsif type.is_a?(RBS::Types::Bases::Void) + 'void' + elsif type.is_a?(RBS::Types::Variable) + "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" + elsif type.is_a?(RBS::Types::ClassInstance) #&& !type.args.empty? + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Bases::Instance) + 'self' + elsif type.is_a?(RBS::Types::Bases::Top) + # top is the most super superclass + 'BasicObject' + elsif type.is_a?(RBS::Types::Bases::Bottom) + # bottom is used in contexts where nothing will ever return + # - e.g., it could be the return type of 'exit()' or 'raise' + # + # @todo define a specific bottom type and use it to + # determine dead code + 'undefined' + elsif type.is_a?(RBS::Types::Intersection) + type.types.map { |member| other_type_to_tag(member) }.join(', ') + elsif type.is_a?(RBS::Types::Proc) + 'Proc' + elsif type.is_a?(RBS::Types::Alias) + # type-level alias use - e.g., 'bool' in "type bool = true | false" + # @todo ensure these get resolved after processing all aliases + # @todo handle recursive aliases + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::Interface) + # represents a mix-in module which can be considered a + # subtype of a consumer of it + type_tag(type.name, type.args) + elsif type.is_a?(RBS::Types::ClassSingleton) + # e.g., singleton(String) + type_tag(type.name) + else + Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" + 'undefined' + end end # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] @@ -754,11 +945,11 @@ def add_mixins decl, namespace decl.each_mixin do |mixin| # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend - type = RbsTranslator.build_unique_type(mixin.name, mixin.args) + type = build_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) pins.push klass.new( name: mixin.name.relative!.to_s, - type_location: RbsTranslator.to_sg_location(mixin.location), + type_location: location_decl_to_pin_location(mixin.location), generic_values: generic_values, closure: namespace, source: :rbs From b2543b96cde2e461268ea1fece5e72d2ffdf7d16 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:12:24 -0400 Subject: [PATCH 44/53] Cache command checks workspace for RBS config --- lib/solargraph/shell.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index b4ead7884..f09c0efeb 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -112,12 +112,10 @@ def cache gem, version = nil PinCache.serialize_yard_gem(gemspec, pins) end - workspace = Solargraph::Workspace.new(Dir.pwd) - rbs_map = RbsMap.from_gemspec(gemspec, workspace.rbs_collection_path, workspace.rbs_collection_config_path) + workspace = Solargraph::Workspace.new(Dir.pwd) if File.exist?('rbs_collection.yaml') + rbs_map = RbsMap.from_gemspec(gemspec, workspace&.rbs_collection_path, workspace&.rbs_collection_config_path) if options[:rebuild] || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key) - # cache pins even if result is zero, so we don't retry building pins - pins = rbs_map.pins || [] - PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, pins) + PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, rbs_map.pins) end rescue Gem::MissingSpecError warn "Gem '#{gem}' not found" From f6bb23f729eb01abac5a9fe3cb24f01c03b3b209 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:12:42 -0400 Subject: [PATCH 45/53] Conversions use RbsTranslator.to_complex_type --- lib/solargraph/rbs_map/conversions.rb | 37 +++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index bda520240..8d2a396b1 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -275,8 +275,8 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - tag = other_type_to_tag param.default_type - generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted + complex_type = RbsTranslator.to_complex_type(param.default_type).force_rooted + generic_defaults[param.name.to_s] = complex_type end end @@ -429,7 +429,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -593,7 +593,7 @@ def parts_of_function type, pin if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) return [ [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - ComplexType.try_parse(method_type_to_tag(type)).force_rooted + method_type_to_tag(type).force_rooted ] end @@ -603,21 +603,20 @@ def parts_of_function type, pin # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_positionals.each do |param| # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, type_location: type_location, source: :rbs) end if type.type.rest_positionals name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = - ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + inner_rest_positional_type = RbsTranslator.to_complex_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], [inner_rest_positional_type], @@ -636,7 +635,7 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_keywords.each do |orig, param| @@ -644,7 +643,7 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, type_location: type_location, source: :rbs) end @@ -655,7 +654,7 @@ def parts_of_function type, pin end rooted_tag = method_type_to_tag(type) - return_type = ComplexType.try_parse(rooted_tag).force_rooted + return_type = rooted_tag.force_rooted [parameters, return_type] end @@ -677,7 +676,7 @@ def attr_reader_to_pin decl, closure, context visibility: visibility, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug do "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" @@ -708,12 +707,12 @@ def attr_writer_to_pin decl, closure, context pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: ComplexType.try_parse(other_type_to_tag(decl.type)).force_rooted, + return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -738,7 +737,7 @@ def ivar_to_pin decl, closure comments: decl.comment&.string, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -755,7 +754,7 @@ def cvar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -772,7 +771,7 @@ def civar_to_pin decl, closure type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = RbsTranslator.to_complex_type(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -847,9 +846,9 @@ def alias_to_pin decl, closure # @return [String] def method_type_to_tag type if type_aliases.key?(type.type.return_type.to_s) - other_type_to_tag(type_aliases[type.type.return_type.to_s].type) + RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) else - other_type_to_tag type.type.return_type + RbsTranslator.to_complex_type(type.type.return_type) end end From e5247f4190adf2e45dec651102f5360e8e5d6ab1 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:31:12 -0400 Subject: [PATCH 46/53] Redundant code --- lib/solargraph/rbs_map/conversions.rb | 75 +-------------------------- 1 file changed, 2 insertions(+), 73 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 8d2a396b1..0e86750c4 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -413,7 +413,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = other_type_to_tag(decl.type) + tag = RbsTranslator.to_complex_type(decl.type).to_s pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -857,9 +857,7 @@ def method_type_to_tag type # @return [ComplexType::UniqueType] def build_type(type_name, type_args = []) base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| other_type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted - end + params = type_args.map { |arg| RbsTranslator.to_complex_type(arg).force_rooted } if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) else @@ -867,75 +865,6 @@ def build_type(type_name, type_args = []) end end - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [String] - def type_tag(type_name, type_args = []) - build_type(type_name, type_args).tags - end - - # @param type [RBS::Types::Bases::Base] - # @return [String] - def other_type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{other_type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) - 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) - type.literal.inspect - elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| other_type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) - # @todo Better record support - 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) - 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) - 'void' - elsif type.is_a?(RBS::Types::Variable) - "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) #&& !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) - 'self' - elsif type.is_a?(RBS::Types::Bases::Top) - # top is the most super superclass - 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) - # bottom is used in contexts where nothing will ever return - # - e.g., it could be the return type of 'exit()' or 'raise' - # - # @todo define a specific bottom type and use it to - # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| other_type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) - 'Proc' - elsif type.is_a?(RBS::Types::Alias) - # type-level alias use - e.g., 'bool' in "type bool = true | false" - # @todo ensure these get resolved after processing all aliases - # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) - # represents a mix-in module which can be considered a - # subtype of a consumer of it - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) - # e.g., singleton(String) - type_tag(type.name) - else - Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' - end - end - # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module] # @param namespace [Pin::Namespace, nil] # @return [void] From 4f12505902ba949ebc51414344d0b6db53eb79e5 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 09:02:19 -0400 Subject: [PATCH 47/53] Redundant code --- lib/solargraph/rbs_map/conversions.rb | 71 ++------------------------- 1 file changed, 4 insertions(+), 67 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 0e86750c4..128b751cf 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -589,73 +589,10 @@ def location_decl_to_pin_location(location) # @param pin [Pin::Method] # @return [Array(Array, ComplexType)] def parts_of_function type, pin - type_location = pin.type_location - if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) - return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - method_type_to_tag(type).force_rooted - ] - end - - parameters = [] - arg_num = -1 - type.type.required_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: RbsTranslator.to_complex_type(param.type).force_rooted, source: :rbs, type_location: type_location) - end - type.type.optional_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_positionals - name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = RbsTranslator.to_complex_type(type.type.rest_positionals.type) - rest_positional_type = ComplexType::UniqueType.new('Array', - [], - [inner_rest_positional_type], - rooted: true, parameters_type: :list) - parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, - source: :rbs, type_location: type_location, - return_type: rest_positional_type,) - end - type.type.trailing_positionals.each do |param| - # @sg-ignore RBS generic type understanding issue - name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) - end - type.type.required_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - source: :rbs, type_location: type_location) - end - type.type.optional_keywords.each do |orig, param| - # @sg-ignore RBS generic type understanding issue - name = orig ? orig.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, - # @sg-ignore RBS generic type understanding issue - return_type: RbsTranslator.to_complex_type(param.type).force_rooted, - type_location: type_location, - source: :rbs) - end - if type.type.rest_keywords - name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, - source: :rbs, type_location: type_location) - end - - rooted_tag = method_type_to_tag(type) - return_type = rooted_tag.force_rooted - [parameters, return_type] + [ + RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), + method_type_to_tag(type).force_rooted + ] end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] From 4a342795bc51f8215168bc92f6a2a27b147453f2 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 09:46:29 -0400 Subject: [PATCH 48/53] Type fix --- lib/solargraph/rbs_map/conversions.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 128b751cf..11552c048 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -591,7 +591,7 @@ def location_decl_to_pin_location(location) def parts_of_function type, pin [ RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), - method_type_to_tag(type).force_rooted + extract_method_type_return_type(type).force_rooted ] end @@ -779,9 +779,13 @@ def alias_to_pin decl, closure 'NilClass' => 'nil' } + # Extract a ComplexType from a MethodType's return type. + # + # This method will convert type aliases to concrete types. + # # @param type [RBS::MethodType] - # @return [String] - def method_type_to_tag type + # @return [ComplexType] + def extract_method_type_return_type type if type_aliases.key?(type.type.return_type.to_s) RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) else From ad28b362e034c7287114f71111c6d0f1c442a3d1 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 May 2026 11:48:42 -0400 Subject: [PATCH 49/53] Updates for 0.59.0 --- lib/solargraph/rbs_map/conversions.rb | 16 ++++++++-------- spec/rbs_map/core_map_spec.rb | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 11552c048..7fd75cdc7 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -67,7 +67,6 @@ def convert_decl_to_pin decl, closure unless closure.name == '' || decl.name.absolute? Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on interface #{decl.inspect}") end - # STDERR.puts "Skipping interface #{decl.name.relative!}" interface_decl_to_pin decl when RBS::AST::Declarations::TypeAlias # @sg-ignore flow sensitive typing should support case/when @@ -80,7 +79,7 @@ def convert_decl_to_pin decl, closure # @sg-ignore Wrong argument type for Solargraph::Pin::Reference::TypeAlias.new: return_type expected Solargraph::ComplexType, received Solargraph::ComplexType::UniqueType, Solargraph::ComplexType Solargraph::Pin::Reference::TypeAlias.new( # @sg-ignore Unresolved calls to name, type, type_location; return_type type mismatch - name: ComplexType.try_parse(decl.name.to_s).to_s, return_type: other_type_to_type(decl.type).force_rooted, closure: closure, source: :rbs, type_location: location_decl_to_pin_location(decl.location) + name: ComplexType.try_parse(decl.name.to_s).to_s, return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, closure: closure, source: :rbs, type_location: location_decl_to_pin_location(decl.location) ) ) when RBS::AST::Declarations::Module @@ -169,7 +168,8 @@ def build_type type_name, type_args = [] rbs_name = type_name.relative!.to_s base = RBS_TO_CLASS.fetch(rbs_name, rbs_name) - params = type_args.map { |a| other_type_to_type(a) } + params = type_args.map { |a| RbsTranslator.to_complex_type(a) } + # @todo Tuples are in flux # tuples have their own class and are handled in other_type_to_type if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: type_name.absolute?, @@ -413,7 +413,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsTranslator.to_complex_type(decl.type).to_s + tag = RbsTranslator.to_complex_type(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -786,11 +786,11 @@ def alias_to_pin decl, closure # @param type [RBS::MethodType] # @return [ComplexType] def extract_method_type_return_type type - if type_aliases.key?(type.type.return_type.to_s) - RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) - else + # if type_aliases.key?(type.type.return_type.to_s) + # RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) + # else RbsTranslator.to_complex_type(type.type.return_type) - end + # end end # @param type_name [RBS::TypeName] diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index 79878c572..a3769f70a 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -82,7 +82,7 @@ # correctly. It would be better to test RbsMap or RbsMap::Conversions # with an RBS fixture. core_map = described_class.new - pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == '::Enumerable' } + pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == 'Enumerable' } expect(pins.map(&:closure).map(&:namespace)).to include('Enumerator') end From 1e3cba4abfda6cee800715e939d3f62f86e1c4b2 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 May 2026 18:54:54 -0400 Subject: [PATCH 50/53] Merge fixes --- lib/solargraph/rbs_map/conversions.rb | 101 +++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 7fd75cdc7..c17874cf5 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -559,13 +559,15 @@ def method_def_to_pin decl, closure, context # @param pin [Pin::Method] # @return [void] def method_def_to_sigs decl, pin - # @param overload [RBS::AST::Members::MethodDefinition::Overload] + # rubocop:disable Style/SafeNavigationChainLength + implicit_nil = decl.overloads.first&.annotations&.map(&:string)&.include?('implicitly-returns-nil') || false + # rubocop:enable Style/SafeNavigationChainLength # @param overload [RBS::AST::Members::MethodDefinition::Overload] decl.overloads.map do |overload| type_location = location_decl_to_pin_location(overload.method_type.location) generics = overload.method_type.type_params.map(&:name).map(&:to_s) - signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) + signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin, implicit_nil) block = if overload.method_type.block - block_parameters, block_return_type = parts_of_function(overload.method_type.block, pin) + block_parameters, block_return_type = parts_of_function(overload.method_type.block, pin, implicit_nil) Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) end @@ -585,13 +587,92 @@ def location_decl_to_pin_location(location) Location.new(location.name.to_s, range) end + # @param type [RBS::MethodType, RBS::Types::Block] + # @param pin [Pin::Method] + # @param implicit_nil [Boolean] + # @return [Array(Array, ComplexType)] + def parts_of_function type, pin, implicit_nil + type_location = pin.type_location + if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) + return [ + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, + type_location: type_location)], + method_type_to_type(type, implicit_nil) + ] + end + + parameters = [] + arg_num = -1 + type.type.required_positionals.each do |param| + # @sg-ignore Unresolved call to name + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: other_type_to_type(param.type), + source: :rbs, type_location: type_location) + end + type.type.optional_positionals.each do |param| + # @sg-ignore Unresolved call to name + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: other_type_to_type(param.type), + type_location: type_location, + source: :rbs) + end + if type.type.rest_positionals + name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" + inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) + rest_positional_type = ComplexType::UniqueType.new('Array', + [], + [inner_rest_positional_type], + rooted: true, parameters_type: :list) + parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, + source: :rbs, type_location: type_location, + return_type: rest_positional_type) + end + type.type.trailing_positionals.each do |param| + # @sg-ignore Unresolved call to name + name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, + type_location: type_location) + end + type.type.required_keywords.each do |orig, param| + # @sg-ignore Unresolved call to to_s + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: other_type_to_type(param.type), + source: :rbs, type_location: type_location) + end + type.type.optional_keywords.each do |orig, param| + # @sg-ignore Unresolved call to to_s + name = orig ? orig.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: other_type_to_type(param.type), + type_location: type_location, + source: :rbs) + end + if type.type.rest_keywords + name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, + name: type.type.rest_keywords.name.to_s, closure: pin, + source: :rbs, type_location: type_location) + end + + return_type = method_type_to_type(type, implicit_nil) + [parameters, return_type] + end + # @param type [RBS::MethodType,RBS::Types::Block] # @param pin [Pin::Method] + # @param implicit_nil [Boolean] # @return [Array(Array, ComplexType)] - def parts_of_function type, pin + def parts_of_function type, pin, implicit_nil [ RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names), - extract_method_type_return_type(type).force_rooted + extract_method_type_return_type(type, implicit_nil).force_rooted ] end @@ -785,12 +866,10 @@ def alias_to_pin decl, closure # # @param type [RBS::MethodType] # @return [ComplexType] - def extract_method_type_return_type type - # if type_aliases.key?(type.type.return_type.to_s) - # RbsTranslator.to_complex_type(type_aliases[type.type.return_type.to_s].type) - # else - RbsTranslator.to_complex_type(type.type.return_type) - # end + def extract_method_type_return_type type, implicit_nil + tag = RbsTranslator.to_complex_type(type.type.return_type) + return ComplexType.parse("#{tag}, nil") if tag && implicit_nil + tag end # @param type_name [RBS::TypeName] From 1907985d51b912b81ecc5a6301bf8a8e5ebada50 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 May 2026 19:19:20 -0400 Subject: [PATCH 51/53] Private constant --- lib/solargraph/rbs_map/conversions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index c17874cf5..377aa1b30 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -859,6 +859,7 @@ def alias_to_pin decl, closure 'untyped' => '', 'NilClass' => 'nil' } + private_constant :RBS_TO_YARD_TYPE # Extract a ComplexType from a MethodType's return type. # From d21efd813a5fefd4c0398cba4884953758a7b1d5 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Tue, 19 May 2026 20:23:52 -0400 Subject: [PATCH 52/53] Linting --- lib/solargraph/pin/method.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index ae7204d9c..b028188ea 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -4,7 +4,6 @@ module Solargraph module Pin # The base class for method and attribute pins. # - # rubocop:disable Metrics/ClassLength class Method < Callable include Solargraph::Parser::NodeMethods @@ -190,10 +189,10 @@ def generate_signature parameters, return_type # @return [::Array] def signatures @signatures ||= if inline_rbs.empty? - signatures_from_yard - else - signatures_from_inline_rbs - end + signatures_from_yard + else + signatures_from_inline_rbs + end end # @param return_type [ComplexType] @@ -445,8 +444,6 @@ def rest_of_stack api_map attr_writer :block, :signature_help, :documentation, :return_type - attr_writer :return_type - def dodgy_visibility_source? # as of 2025-03-12, the RBS generator used for # e.g. activesupport did not understand 'private' markings @@ -752,6 +749,5 @@ def inline_rbs .join("\n") end end - # rubocop:enable Metrics/ClassLength end end From 1cbde41189d204ffbdf6d9662aa86047b26489ca Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 20 May 2026 07:47:39 -0400 Subject: [PATCH 53/53] RBS inheritance parameters --- .../node_processors/namespace_node.rb | 33 ++++++++++++++++--- spec/parser/node_processor_spec.rb | 9 +++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb b/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb index 8051dfee8..d09539275 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb @@ -8,22 +8,26 @@ class NamespaceNode < Parser::NodeProcessor::Base include ParserGem::NodeMethods def process - superclass_name = nil - superclass_name = unpack_name(node.children[1]) if node.type == :class && node.children[1]&.type == :const + name = unpack_name(node.children[0]) + comments = comments_for(node) + superclass_name = if node.type == :class + "#{type_from_node}#{parameters_from_inline_rbs}" + end loc = get_node_location(node) nspin = Solargraph::Pin::Namespace.new( type: node.type, location: loc, closure: region.closure, - name: unpack_name(node.children[0]), - comments: comments_for(node), + name: name, + comments: comments, visibility: :public, gates: region.closure.gates.freeze, source: :parser ) pins.push nspin - unless superclass_name.nil? + Solargraph.logger.warn "Superclass: #{superclass_name}" if superclass_name&.start_with?('Array') + if superclass_name pins.push Pin::Reference::Superclass.new( location: loc, closure: pins.last, @@ -33,6 +37,25 @@ def process end process_children region.update(closure: nspin, visibility: :public) end + + private + + # @param comments [String] + # @return [String, nil] + def parameters_from_inline_rbs + source = region.source.code_for(node) + match = source.match(/[^\n]*?#\s?+\[([^\]]*)/) + return unless match && match[1] + + code = match[1].strip + return if code.empty? + + "<#{code}>" + end + + def type_from_node + unpack_name(node.children[1]) if node.children[1]&.type == :const + end end end end diff --git a/spec/parser/node_processor_spec.rb b/spec/parser/node_processor_spec.rb index da7031779..b32371ff1 100644 --- a/spec/parser/node_processor_spec.rb +++ b/spec/parser/node_processor_spec.rb @@ -70,4 +70,13 @@ def some_method; end described_class.deregister(:def, dummy_processor1) described_class.deregister(:def, dummy_processor2) end + + it 'parses RBS parameters for classes' do + map = Solargraph::SourceMap.load_string(%( + class Foo < Array #[String] + end + ), 'test.rb') + + expect(map.pins.last.type.to_s).to eq('Array') + end end