Skip to content

Commit

Permalink
Index method parameters using RBS
Browse files Browse the repository at this point in the history
  • Loading branch information
andyw8 committed Jun 17, 2024
1 parent 739c378 commit 9d1b732
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 3 deletions.
110 changes: 107 additions & 3 deletions lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ def add_declaration_mixins_to_entry(declaration, entry)
end
end

sig { params(member: RBS::AST::Members::MethodDefinition, owner: Entry::Namespace).void }
def handle_method(member, owner)
sig { params(member: RBS::AST::Members::MethodDefinition, entry: Entry::Namespace).void }
def handle_method(member, entry)
name = member.name.name
file_path = member.location.buffer.name
location = to_ruby_indexer_location(member.location)
Expand All @@ -122,8 +122,17 @@ def handle_method(member, owner)
Entry::Visibility::PUBLIC
end

owner = entry
real_owner = member.singleton? ? existing_or_new_singleton_klass(owner) : owner
@index.add(Entry::Method.new(name, file_path, location, comments, [], visibility, real_owner))
@index.add(Entry::Method.new(
name,
file_path,
location,
comments,
build_parameters(member.overloads),
visibility,
real_owner,
))
end

sig { params(owner: Entry::Namespace).returns(T.nilable(Entry::Class)) }
Expand All @@ -143,5 +152,100 @@ def existing_or_new_singleton_klass(owner)
@index.add(entry, skip_prefix_tree: true)
entry
end

sig do
params(overloads: T::Array[RBS::AST::Members::MethodDefinition::Overload]).returns(T::Array[Entry::Parameter])
end
def build_parameters(overloads)
parameters = {}
overloads.each do |overload|
process_overload(overload, parameters)
end
parameters.values
end

sig do
params(
overload: RBS::AST::Members::MethodDefinition::Overload,
parameters: T::Hash[Symbol, Entry::Parameter],
).void
end
def process_overload(overload, parameters)
function = overload.method_type.type
process_required_positionals(function, parameters) if function.required_positionals
process_optional_positionals(function, parameters) if function.optional_positionals
process_trailing_positionals(function, parameters) if function.trailing_positionals
process_required_keywords(function, parameters) if function.trailing_positionals
process_optional_keywords(function, parameters) if function.optional_keywords
process_rest_keywords(function, parameters) if function.rest_keywords
end

sig { params(function: RBS::Types::Function, parameters: T::Hash[Symbol, Entry::Parameter]).void }
def process_required_positionals(function, parameters)
function.required_positionals.each do |param|
name = param.name

next unless name

parameters[name] = Entry::RequiredParameter.new(name: name)
end
diff = parameters.keys - function.required_positionals.map(&:name)
diff.each do |d|
parameters[d] = Entry::OptionalParameter.new(name: d)
end
end

sig { params(function: RBS::Types::Function, parameters: T::Hash[Symbol, Entry::Parameter]).void }
def process_optional_positionals(function, parameters)
function.optional_positionals.each do |param|
name = param.name
next unless name

parameters[name] = Entry::OptionalParameter.new(name: name)
end
rest = function.rest_positionals

if rest
rest_name = rest.name || Entry::RestParameter::DEFAULT_NAME
return if rest_name == :selector_0

parameters[rest_name] = Entry::RestParameter.new(name: rest_name)
end
end

sig { params(function: RBS::Types::Function, parameters: T::Hash[Symbol, Entry::Parameter]).void }
def process_trailing_positionals(function, parameters)
function.trailing_positionals.each do |param|
name = param.name
parameters[name] = Entry::OptionalParameter.new(name: param.name)
end
end

sig { params(function: RBS::Types::Function, parameters: T::Hash[Symbol, Entry::Parameter]).void }
def process_required_keywords(function, parameters)
function.required_keywords.each do |param|
name = param.first
parameters[name] = Entry::KeywordParameter.new(name: name)
end
end

sig { params(function: RBS::Types::Function, parameters: T::Hash[Symbol, Entry::Parameter]).void }
def process_optional_keywords(function, parameters)
function.optional_keywords.each do |param|
name = param.first.to_s.to_sym # hack
parameters[name] = Entry::OptionalKeywordParameter.new(name: name)
end
end

sig { params(function: RBS::Types::Function, parameters: T::Hash[Symbol, Entry::Parameter]).void }
def process_rest_keywords(function, parameters)
keyword_rest = function.rest_keywords

keyword_rest_name = keyword_rest.name || Entry::KeywordRestParameter::DEFAULT_NAME
parameters[keyword_rest_name] = Entry::KeywordRestParameter.new(name: keyword_rest_name)
end
# TODO: Blocks

# @index.add(Entry::Method.new(name, file_path, location, comments, [], visibility, owner))
end
end
86 changes: 86 additions & 0 deletions lib/ruby_indexer/test/rbs_indexer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,92 @@ def test_index_methods
assert_operator(entry.location.end_column, :>, 0)
end

def test_rbs_method_with_required_positionals
entries = @index["crypt"] # https://rubyapi.org/3.3/o/string#method-i-crypt
assert_equal(1, entries.length)

entry = entries.first
parameters = entry.parameters

assert_equal(1, parameters.length)
assert_kind_of(Entry::RequiredParameter, parameters[0])
assert_equal(:salt_str, parameters[0].name)
end

def test_rbs_method_with_optional_parameter
entries = @index["chomp"] # https://rubyapi.org/3.3/o/string#method-i-chomp
assert_equal(1, entries.length)

entry = entries.first
parameters = entry.parameters

assert_equal(1, parameters.length)
assert_kind_of(Entry::OptionalParameter, parameters[0])
assert_equal(:separator, parameters[0].name)
end

def test_rbs_method_with_required_and_optional_parameters
entries = @index["gsub"] # https://rubyapi.org/3.3/o/string#method-i-gsub
assert_equal(1, entries.length)

entry = entries.first

parameters = entry.parameters

assert_equal(2, parameters.length)
assert_kind_of(Entry::RequiredParameter, parameters[0])
assert_kind_of(Entry::OptionalParameter, parameters[1])
assert_equal(:pattern, parameters[0].name)
assert_equal(:replacement, parameters[1].name)
end

def test_rbs_method_with_rest_positionals
entries = @index["count"] # https://rubyapi.org/3.3/o/string#method-i-count
entry = entries.find { |entry| entry.owner.name == "String" }

parameters = entry.parameters

# TODO: In RBS, this is represented as having two arguments:
#
# def count: (selector selector_0, *selector more_selectors) -> Integer
#
# but perhaps that is confusing?
assert_equal(2, parameters.length)
assert_kind_of(RubyIndexer::Entry::RequiredParameter, parameters[0])
assert_kind_of(RubyIndexer::Entry::RestParameter, parameters[1])
end

def test_rbs_method_with_trailing_positionals
entries = @index["select"] # https://rubyapi.org/3.3/o/io#method-c-select
entry = entries.find { |entry| entry.owner.name == "IO::<Class:IO>" }

parameters = entry.parameters

assert_equal(4, parameters.length)
assert_kind_of(Entry::RequiredParameter, parameters[0])
assert_kind_of(Entry::OptionalParameter, parameters[1])
assert_kind_of(Entry::OptionalParameter, parameters[2])
assert_kind_of(Entry::OptionalParameter, parameters[3])
end

def test_rbs_method_with_optional_keywords
entries = @index["step"] # https://rubyapi.org/3.3/o/numeric#method-i-step
entry = entries.find { |entry| entry.owner.name == "Numeric" }

parameters = entry.parameters

assert_equal(4, parameters.length)
assert_equal([:limit, :step, :by, :to], parameters.map(&:name))
assert_kind_of(Entry::OptionalParameter, parameters[0])
assert_kind_of(Entry::OptionalParameter, parameters[1])
assert_kind_of(Entry::OptionalKeywordParameter, parameters[2])
assert_kind_of(Entry::OptionalKeywordParameter, parameters[3])
end

def test_rbs_method_with_required_keywords
# Investigating if there are any methods in Core for this
end

def test_attaches_correct_owner_to_singleton_methods
entries = @index["basename"]
refute_nil(entries)
Expand Down

0 comments on commit 9d1b732

Please sign in to comment.