Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/solargraph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +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'

dir = File.dirname(__FILE__)
VIEWS_PATH = File.join(dir, 'solargraph', 'views')
Expand Down
52 changes: 42 additions & 10 deletions lib/solargraph/pin/method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -175,7 +176,7 @@ def symbol_kind
end

def return_type
@return_type ||= 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<Parameter>]
Expand Down Expand Up @@ -219,13 +220,10 @@ def generate_signature(parameters, return_type)

# @return [::Array<Signature>]
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
@signatures ||= if inline_rbs.empty?
signatures_from_yard
else
signatures_from_inline_rbs
end
end

Expand Down Expand Up @@ -468,6 +466,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
Expand Down Expand Up @@ -664,9 +664,41 @@ def concat_example_tags
.concat("```\n")
end

protected
# @return [ComplexType, nil]
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)
rescue RBS::ParsingError
nil
end

attr_writer :return_type
# @return [Array<Pin::Signature>]
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

# @return [Array<Pin::Signature>]
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

# @return [String]
def inline_rbs
comments.lines
.select { |line| line.start_with?(': ') }
.map { |line| line[2..].strip }
.join("\n")
end
end
# rubocop:enable Metrics/ClassLength
end
end
177 changes: 23 additions & 154 deletions lib/solargraph/rbs_map/conversions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -457,74 +457,10 @@ def location_decl_to_pin_location(location)
# @param pin [Pin::Method]
# @return [Array(Array<Pin::Parameter>, 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]
[
RbsTranslator.to_parameter_pins(type, pin, pin.parameter_names),
extract_method_type_return_type(type).force_rooted
]
end

# @param decl [RBS::AST::Members::AttrReader,RBS::AST::Members::AttrAccessor]
Expand All @@ -545,7 +481,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
Expand Down Expand Up @@ -574,12 +510,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
Expand All @@ -604,7 +540,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
Expand All @@ -621,7 +557,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
Expand All @@ -638,7 +574,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
Expand Down Expand Up @@ -705,13 +641,17 @@ 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)
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

Expand All @@ -720,85 +660,14 @@ 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
ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list)
end
end

# @param type_name [RBS::TypeName]
# @param type_args [Enumerable<RBS::Types::Bases::Base>]
# @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]
Expand Down
Loading
Loading