From 52225257b3ce447ece93dbf85d2a4713346e9a6e Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 4 Mar 2026 08:52:33 -0500 Subject: [PATCH 01/23] Parse method return types from inline RBS --- lib/solargraph/pin/method.rb | 21 ++++++++++++++++++--- spec/pin/method_spec.rb | 13 +++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index 86bf1cd09..2c5604d37 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -175,7 +175,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] @@ -468,6 +468,8 @@ def rest_of_stack api_map attr_writer :documentation + 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 @@ -664,9 +666,22 @@ def concat_example_tags .concat("```\n") end - protected + def generate_from_inline_rbs + return nil if inline_rbs.empty? - attr_writer :return_type + 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 283ef6d51..62479a180 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -626,4 +626,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 50bb56aa5a7b84575ac101e5c68f52080aa21a27 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Thu, 5 Mar 2026 07:51:38 -0500 Subject: [PATCH 02/23] RbsToComplex module --- lib/solargraph.rb | 1 + lib/solargraph/pin/method.rb | 3 +- lib/solargraph/rbs_map/conversions.rb | 174 +++++++++++++------------- lib/solargraph/rbs_to_complex.rb | 102 +++++++++++++++ spec/pin/method_spec.rb | 35 +++++- 5 files changed, 225 insertions(+), 90 deletions(-) create mode 100644 lib/solargraph/rbs_to_complex.rb diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 038e7bccf..f358103e0 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 2c5604d37..d6b9c60e0 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -670,8 +670,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 54bca0f73..5f2052cae 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -160,8 +160,7 @@ 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 + generic_defaults[param.name.to_s] = RbsToComplex.convert(param.default_type) end end class_name = decl.name.relative!.to_s @@ -236,7 +235,7 @@ def module_decl_to_pin decl end # @param name [String] - # @param tag [String] + # @param tag [ComplexType] # @param comments [String] # @param decl [RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::Constant, RBS::AST::Declarations::ModuleAlias] # @param base [String, nil] Optional conversion of tag to base @@ -258,8 +257,8 @@ def create_constant(name, tag, comments, decl, base = nil) comments: comments, source: :rbs ) - tag = "#{base}<#{tag}>" if base - rooted_tag = ComplexType.parse(tag).force_rooted.rooted_tags + tag = ComplexType.parse("#{base}<#{tag}>") if base + rooted_tag = tag.force_rooted.rooted_tags constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin end @@ -287,7 +286,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 = RbsToComplex.convert(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -303,7 +302,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 = RbsToComplex.convert(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -461,7 +460,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 @@ -471,21 +470,21 @@ 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: RbsToComplex.convert(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: RbsToComplex.convert(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)) + RbsToComplex.convert(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], [inner_rest_positional_type], @@ -504,7 +503,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: RbsToComplex.convert(param.type).force_rooted, source: :rbs, type_location: type_location) end type.type.optional_keywords.each do |orig, param| @@ -512,7 +511,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: RbsToComplex.convert(param.type).force_rooted, type_location: type_location, source: :rbs) end @@ -522,8 +521,7 @@ def parts_of_function type, pin source: :rbs, type_location: type_location) end - rooted_tag = method_type_to_tag(type) - return_type = ComplexType.try_parse(rooted_tag).force_rooted + return_type = method_type_to_tag(type).force_rooted [parameters, return_type] end @@ -545,7 +543,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 = RbsToComplex.convert(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) logger.debug { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } pins.push pin @@ -574,12 +572,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: RbsToComplex.convert(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 = RbsToComplex.convert(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) pins.push pin end @@ -604,7 +602,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 = RbsToComplex.convert(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -621,7 +619,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 = RbsToComplex.convert(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -638,7 +636,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 = RbsToComplex.convert(decl.type).force_rooted.rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -708,11 +706,13 @@ def alias_to_pin decl, closure # @param type [RBS::MethodType] # @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) - else - other_type_to_tag type.type.return_type - end + RbsToComplex.convert( + 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 type_name [RBS::TypeName] @@ -720,8 +720,8 @@ 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 + params = type_args.map do |a| + RbsToComplex.convert(a).force_rooted end if base == 'Hash' && params.length == 2 ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) @@ -739,65 +739,65 @@ def type_tag(type_name, type_args = []) # @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 + # 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] 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 62479a180..4ab7d522c 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -628,7 +628,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 @@ -638,5 +638,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 55ee5444dbf0ea740a8d44fd3be6cd95395c7639 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 10:38:36 -0500 Subject: [PATCH 03/23] 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 f358103e0..39fc04eb8 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 3a5c46cfcadf81d51247d9d6309154ec20c5d632 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 10:38:50 -0500 Subject: [PATCH 04/23] Generate signatures from inline RBS --- lib/solargraph/pin/method.rb | 25 +++-- lib/solargraph/rbs_map/conversions.rb | 130 ++++---------------------- spec/pin/method_spec.rb | 15 +++ 3 files changed, 51 insertions(+), 119 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index d6b9c60e0..af8d53adb 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -220,12 +220,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? - 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 + 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 end end @@ -668,9 +678,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 5f2052cae..17a0da057 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -93,7 +93,7 @@ def convert_self_types_to_pins decl, module_pin # @param closure [Pin::Namespace] # @return [void] def convert_self_type_to_pins decl, closure - type = build_type(decl.name, decl.args) + 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: decl.name.relative!.to_s, @@ -160,7 +160,7 @@ def class_decl_to_pin decl generic_defaults = {} decl.type_params.each do |param| if param.default_type - generic_defaults[param.name.to_s] = RbsToComplex.convert(param.default_type) + generic_defaults[param.name.to_s] = RbsTranslator.to_complex_type(param.default_type) end end class_name = decl.name.relative!.to_s @@ -179,7 +179,7 @@ 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) + 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( @@ -286,7 +286,7 @@ def module_alias_decl_to_pin decl # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = RbsToComplex.convert(decl.type) + tag = RbsTranslator.to_complex_type(decl.type) pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) end @@ -302,7 +302,7 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsToComplex.convert(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 @@ -470,21 +470,21 @@ 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: RbsToComplex.convert(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: RbsToComplex.convert(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 = - RbsToComplex.convert(type.type.rest_positionals.type) + RbsTranslator.to_complex_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], [inner_rest_positional_type], @@ -503,7 +503,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: RbsToComplex.convert(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| @@ -511,7 +511,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: RbsToComplex.convert(param.type).force_rooted, + return_type: RbsTranslator.to_complex_type(param.type).force_rooted, type_location: type_location, source: :rbs) end @@ -543,7 +543,7 @@ def attr_reader_to_pin(decl, closure, context) visibility: visibility, source: :rbs ) - rooted_tag = RbsToComplex.convert(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 { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } pins.push pin @@ -572,12 +572,12 @@ def attr_writer_to_pin(decl, closure, context) pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: RbsToComplex.convert(decl.type).force_rooted, + return_type: RbsTranslator.to_complex_type(decl.type).force_rooted, source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = RbsToComplex.convert(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 @@ -602,7 +602,7 @@ def ivar_to_pin(decl, closure) comments: decl.comment&.string, source: :rbs ) - rooted_tag = RbsToComplex.convert(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 @@ -619,7 +619,7 @@ def cvar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsToComplex.convert(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 @@ -636,7 +636,7 @@ def civar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = RbsToComplex.convert(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 @@ -645,7 +645,7 @@ 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) + 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: decl.name.relative!.to_s, @@ -695,18 +695,10 @@ 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 - RbsToComplex.convert( + RbsTranslator.to_complex_type( if type_aliases.key?(type.type.return_type.to_s) type_aliases[type.type.return_type.to_s].type else @@ -715,90 +707,6 @@ def method_type_to_tag type ) 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 do |a| - RbsToComplex.convert(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 - - # @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] # @return [void] @@ -807,7 +715,7 @@ 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) + type = RbsTranslator.build_unique_type(mixin.name, mixin.args) generic_values = type.all_params.map(&:to_s) pins.push klass.new( name: mixin.name.relative!.to_s, diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 4ab7d522c..95c11d37c 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -671,5 +671,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 e12d7b409123765cbd41e30c85e7e396fa9f5bb0 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Fri, 6 Mar 2026 23:35:28 -0500 Subject: [PATCH 05/23] Generate all parameters for signatures --- lib/solargraph/pin/method.rb | 35 +++--- lib/solargraph/rbs_map/conversions.rb | 138 ++++------------------- lib/solargraph/rbs_translator.rb | 154 +++++++++++++------------- spec/pin/method_spec.rb | 104 ++++++++++++++++- 4 files changed, 221 insertions(+), 210 deletions(-) diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index af8d53adb..f91a57452 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -175,7 +175,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] @@ -221,20 +221,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 @@ -676,7 +665,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) @@ -684,6 +673,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 17a0da057..7276dc1e5 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -97,7 +97,7 @@ def convert_self_type_to_pins decl, closure generic_values = type.all_params.map(&:to_s) include_pin = Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -169,7 +169,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 @@ -183,7 +183,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: superclass_name, @@ -200,7 +200,7 @@ def class_decl_to_pin decl def interface_decl_to_pin decl, closure class_pin = Solargraph::Pin::Namespace.new( type: :module, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), name: decl.name.relative!.to_s, closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, @@ -221,7 +221,7 @@ def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, name: decl.name.relative!.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, generics: decl.type_params.map(&:name).map(&:to_s), @@ -253,7 +253,7 @@ def create_constant(name, tag, comments, decl, base = nil) constant_pin = Solargraph::Pin::Constant.new( name: name, closure: closure, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), comments: comments, source: :rbs ) @@ -299,7 +299,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 @@ -387,7 +387,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: [], @@ -410,7 +410,7 @@ def method_def_to_pin decl, closure, context 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), visibility: visibility, scope: final_scope, signatures: [], @@ -424,105 +424,9 @@ def method_def_to_pin decl, closure, context # @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| - 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)], - 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] + decl.overloads.map { |overload| RbsTranslator.to_signature(overload.method_type, pin) } end # @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor] @@ -535,7 +439,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, @@ -557,7 +461,7 @@ def attr_writer_to_pin(decl, closure, context) final_scope = decl.kind == :instance ? :instance : :class name = "#{decl.name.to_s}=" 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, @@ -598,7 +502,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 ) @@ -616,7 +520,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 @@ -633,7 +537,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 @@ -649,7 +553,7 @@ def include_to_pin decl, closure generic_values = type.all_params.map(&:to_s) pins.push Solargraph::Pin::Reference::Include.new( name: decl.name.relative!.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), generic_values: generic_values, closure: closure, source: :rbs @@ -662,7 +566,7 @@ def include_to_pin decl, closure def prepend_to_pin decl, closure pins.push Solargraph::Pin::Reference::Prepend.new( name: decl.name.relative!.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, source: :rbs ) @@ -674,7 +578,7 @@ def prepend_to_pin decl, closure def extend_to_pin decl, closure pins.push Solargraph::Pin::Reference::Extend.new( name: decl.name.relative!.to_s, - type_location: location_decl_to_pin_location(decl.location), + type_location: RbsTranslator.to_sg_location(decl.location), closure: closure, source: :rbs ) @@ -687,7 +591,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, @@ -719,7 +623,7 @@ def add_mixins decl, namespace generic_values = type.all_params.map(&:to_s) pins.push klass.new( name: mixin.name.relative!.to_s, - type_location: location_decl_to_pin_location(mixin.location), + 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 95c11d37c..0152efddf 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -672,7 +672,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 @@ -683,8 +683,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 83ab9095aa2bbf2d88a1c9370b7155e28decb7db Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 03:54:24 -0500 Subject: [PATCH 06/23] 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 39fc04eb8..e0647a58e 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 0152efddf..d554326cd 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -650,17 +650,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 f74dd1f7d608cf197e6669cd1cf16c792ee23f50 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 03:58:21 -0500 Subject: [PATCH 07/23] 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 b72787f96421698383ce1272b94d2889ce8d0184 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 23:56:01 -0500 Subject: [PATCH 08/23] 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 043c4c02a3f2108a7d2543ce3f7b6987fe9ab7d3 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sat, 7 Mar 2026 23:58:58 -0500 Subject: [PATCH 09/23] 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 148465b6f462a8dc5dd3710b5c4dd314bef01358 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:06:35 -0500 Subject: [PATCH 10/23] 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 970a030ee293c7a18721d1deb6060e06bab10007 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:09:50 -0500 Subject: [PATCH 11/23] 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 f91a57452..c1179d300 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 @@ -696,5 +697,6 @@ def inline_rbs .join("\n") end end + # rubocop:enable Metrics/ClassLength end end From 1aa26e25098c66c67b78d2360a37de6a0b453160 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:17:56 -0500 Subject: [PATCH 12/23] 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 c1179d300..cebd89c2d 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -220,12 +220,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 691664d8cd96fc5a6a9c10347430476fdb10caa0 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:43:31 -0500 Subject: [PATCH 13/23] 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 cebd89c2d..a68e8d9fb 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -672,6 +672,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)] @@ -679,6 +680,7 @@ def signatures_from_inline_rbs signatures_from_yard end + # @return [Array] def signatures_from_yard top_type = generate_complex_type result = [] @@ -688,6 +690,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 0a9016e74b8c890b3d1362b1f6d04a7e865d19b2 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:52:52 -0500 Subject: [PATCH 14/23] 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 fd683dfe4ce4aa358e51a7a21bdbf86af1d86653 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 00:56:59 -0500 Subject: [PATCH 15/23] 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 15a2f9d4787e76ee888d339f3037d5f3233072e2 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 01:33:40 -0500 Subject: [PATCH 16/23] 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 a68e8d9fb..2a947e205 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -664,6 +664,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 c3e3e09e33fe7ce56988f6ff8009bb2ffce3529f Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 01:42:48 -0500 Subject: [PATCH 17/23] 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 e342b8f3dec0200616395266fac34bc0814a71ff Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Sun, 8 Mar 2026 08:52:51 -0400 Subject: [PATCH 18/23] Revert Conversions --- lib/solargraph/rbs_map/conversions.rb | 276 ++++++++++++++++++++++---- 1 file changed, 232 insertions(+), 44 deletions(-) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index 7276dc1e5..54bca0f73 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -93,11 +93,11 @@ def convert_self_types_to_pins decl, module_pin # @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 @@ -160,7 +160,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 class_name = decl.name.relative!.to_s @@ -169,7 +170,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 @@ -179,11 +180,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: superclass_name, @@ -200,7 +201,7 @@ def class_decl_to_pin decl def interface_decl_to_pin decl, closure 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, @@ -221,7 +222,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: decl.type_params.map(&:name).map(&:to_s), @@ -235,7 +236,7 @@ def module_decl_to_pin decl end # @param name [String] - # @param tag [ComplexType] + # @param tag [String] # @param comments [String] # @param decl [RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::Constant, RBS::AST::Declarations::ModuleAlias] # @param base [String, nil] Optional conversion of tag to base @@ -253,12 +254,12 @@ def create_constant(name, tag, comments, decl, base = nil) constant_pin = Solargraph::Pin::Constant.new( name: name, closure: closure, - type_location: RbsTranslator.to_sg_location(decl.location), + type_location: location_decl_to_pin_location(decl.location), comments: comments, source: :rbs ) - tag = ComplexType.parse("#{base}<#{tag}>") if base - rooted_tag = tag.force_rooted.rooted_tags + 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 @@ -286,7 +287,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 @@ -299,10 +300,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 @@ -387,7 +388,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: [], @@ -410,7 +411,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: [], @@ -424,9 +425,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] @@ -439,7 +537,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, @@ -447,7 +545,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 { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } pins.push pin @@ -461,7 +559,7 @@ def attr_writer_to_pin(decl, closure, context) final_scope = decl.kind == :instance ? :instance : :class name = "#{decl.name.to_s}=" 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, @@ -476,12 +574,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 @@ -502,11 +600,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 @@ -520,10 +618,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 @@ -537,10 +635,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 @@ -549,11 +647,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 @@ -566,7 +664,7 @@ def include_to_pin decl, closure def prepend_to_pin decl, closure 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 ) @@ -578,7 +676,7 @@ def prepend_to_pin decl, closure def extend_to_pin decl, closure 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 ) @@ -591,7 +689,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, @@ -599,16 +697,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] @@ -619,11 +807,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 bd18ee7ffb3c94ad76cbab093ddf59797660462c Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:12:24 -0400 Subject: [PATCH 19/23] 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 2699dad73..5c69d4a2b 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -111,12 +111,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 end From 9fee43769c2e14479aff4c7103f01e52a6e50d3c Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:12:42 -0400 Subject: [PATCH 20/23] 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 54bca0f73..180ec5474 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -160,8 +160,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 class_name = decl.name.relative!.to_s @@ -303,7 +303,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 @@ -461,7 +461,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 @@ -471,21 +471,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], @@ -504,7 +503,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| @@ -512,7 +511,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 @@ -523,7 +522,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 @@ -545,7 +544,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 { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } pins.push pin @@ -574,12 +573,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 @@ -604,7 +603,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 @@ -621,7 +620,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 @@ -638,7 +637,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 @@ -709,9 +708,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 45f36e8c98fab65d6e91ab5501ab601be42e4a2c Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 08:31:12 -0400 Subject: [PATCH 21/23] 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 180ec5474..6ea45ea8a 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -287,7 +287,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 @@ -719,9 +719,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 @@ -729,75 +727,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] # @return [void] From 25ca7291daab531d410782c565f354a39b3438fe Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 09:02:19 -0400 Subject: [PATCH 22/23] 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 6ea45ea8a..e4cbb910c 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -457,73 +457,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 ea98de3d866b6e5141ed764dbf04e46cd6abc942 Mon Sep 17 00:00:00 2001 From: Fred Snyder Date: Wed, 11 Mar 2026 09:46:29 -0400 Subject: [PATCH 23/23] 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 e4cbb910c..a63722864 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -459,7 +459,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 @@ -641,9 +641,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