Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
st0012 committed Aug 27, 2024
1 parent 6b05e67 commit 88f2cbb
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 32 deletions.
4 changes: 2 additions & 2 deletions lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ def on_def_node_enter(node)
node.location,
node.name_loc,
comments,
[Entry::Signature.new(list_params(node.parameters))],
[Entry::Signature.new(list_params(node.parameters), ["Object"])],
current_visibility,
@owner_stack.last,
))
Expand All @@ -343,7 +343,7 @@ def on_def_node_enter(node)
node.location,
node.name_loc,
comments,
[Entry::Signature.new(list_params(node.parameters))],
[Entry::Signature.new(list_params(node.parameters), ["Object"])],
current_visibility,
singleton,
))
Expand Down
11 changes: 8 additions & 3 deletions lib/ruby_indexer/lib/ruby_indexer/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def signatures
begin
params = []
params << RequiredParameter.new(name: name.delete_suffix("=").to_sym) if name.end_with?("=")
[Entry::Signature.new(params)]
[Entry::Signature.new(params, ["Object"])]
end,
T.nilable(T::Array[Signature]),
)
Expand Down Expand Up @@ -576,9 +576,14 @@ class Signature
sig { returns(T::Array[Parameter]) }
attr_reader :parameters

sig { params(parameters: T::Array[Parameter]).void }
def initialize(parameters)
sig { returns(T::Array[String]) }
attr_reader :return_types

sig { params(parameters: T::Array[Parameter], return_types: T::Array[String]).void }
def initialize(parameters, return_types)
@parameters = parameters
# Return types should only be used to assist type inference, so we don't want to display them in signature help
@return_types = return_types
end

# Returns a string with the decorated names of the parameters of this member. E.g.: `(a, b = 1, c: 2)`
Expand Down
44 changes: 33 additions & 11 deletions lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,42 @@ def handle_method(member, owner)
sig { params(member: RBS::AST::Members::MethodDefinition).returns(T::Array[Entry::Signature]) }
def signatures(member)
member.overloads.map do |overload|
parameters = process_overload(overload)
Entry::Signature.new(parameters)
end
end
function = T.cast(overload.method_type.type, RBS::Types::Function)
parameters = parse_arguments(function)

sig { params(overload: RBS::AST::Members::MethodDefinition::Overload).returns(T::Array[Entry::Parameter]) }
def process_overload(overload)
function = T.cast(overload.method_type.type, RBS::Types::Function)
parameters = parse_arguments(function)
block = overload.method_type.block
parameters << Entry::BlockParameter.anonymous if block&.required

block = overload.method_type.block
parameters << Entry::BlockParameter.anonymous if block&.required
rbs_return_type = function.return_type

parameters
return_types = rbs_type_to_ruby_types(rbs_return_type)

Entry::Signature.new(parameters, return_types)
end
end

sig do
params(rbs_type: T.untyped).returns(T::Array[String])
end
def rbs_type_to_ruby_types(rbs_type)
case rbs_type
when RBS::Types::Bases::Void, RBS::Types::Bases::Nil
["NilClass"]
when RBS::Types::Bases::Self
["self"]
when RBS::Types::Bases::Bool
["TrueClass", "FalseClass"]
when RBS::Types::Optional
rbs_type_to_ruby_types(rbs_type.type)
when RBS::Types::Union, RBS::Types::Tuple, RBS::Types::Intersection
rbs_type.types.map { |type| rbs_type_to_ruby_types(type) }.flatten
else
if rbs_type.respond_to?(:name)
[rbs_type.name.name.to_s]
else
["Object"]
end
end
end

sig { params(function: RBS::Types::Function).returns(T::Array[Entry::Parameter]) }
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_indexer/test/enhancements_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def on_call_node(index, owner, node, file_path)
location,
location,
[],
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
[Entry::Signature.new([Entry::RequiredParameter.new(name: :a)], ["Object"])],
Entry::Visibility::PUBLIC,
owner,
))
Expand Down
59 changes: 44 additions & 15 deletions lib/ruby_lsp/type_inferrer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,30 @@ def infer_receiver_for_call_node(node, node_context)
Type.new("Range")
when Prism::LambdaNode
Type.new("Proc")
when Prism::CallNode
method_name = receiver.message
return unless method_name

inferred_receiver_type = infer_receiver_for_call_node(receiver, node_context)

if inferred_receiver_type
methods = @index.resolve_method(method_name, inferred_receiver_type.name, inherited_only: false)

if methods
method = methods.first
return unless method

first_signature = method.signatures.first
return unless first_signature

return_type = first_signature.return_types.first
return unless return_type

Type.new(return_type)
else
infer_with_guessed_types(node, node_context)
end
end
when Prism::ConstantPathNode, Prism::ConstantReadNode
# When the receiver is a constant reference, we have to try to resolve it to figure out the right
# receiver. But since the invocation is directly on the constant, that's the singleton context of that
Expand All @@ -93,22 +117,27 @@ def infer_receiver_for_call_node(node, node_context)

Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
else
return unless @experimental_features

raw_receiver = node.receiver&.slice

if raw_receiver
guessed_name = raw_receiver
.delete_prefix("@")
.delete_prefix("@@")
.split("_")
.map(&:capitalize)
.join
infer_with_guessed_types(node, node_context)
end
end

entries = @index.resolve(guessed_name, node_context.nesting) || @index.first_unqualified_const(guessed_name)
name = entries&.first&.name
GuessedType.new(name) if name
end
sig { params(node: Prism::CallNode, node_context: NodeContext).returns(T.nilable(Type)) }
def infer_with_guessed_types(node, node_context)
return unless @experimental_features

raw_receiver = node.receiver&.slice

if raw_receiver
guessed_name = raw_receiver
.delete_prefix("@")
.delete_prefix("@@")
.split("_")
.map(&:capitalize)
.join

entries = @index.resolve(guessed_name, node_context.nesting) || @index.first_unqualified_const(guessed_name)
name = entries&.first&.name
GuessedType.new(name) if name
end
end

Expand Down
15 changes: 15 additions & 0 deletions test/type_inferrer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,21 @@ def test_infer_string_literal
assert_equal("String", @type_inferrer.infer_receiver_type(node_context).name)
end

def test_infer_chained_call_with_literal_receiver
RubyIndexer::RBSIndexer.new(@index).index_ruby_core
node_context = index_and_locate(<<~RUBY, { line: 0, character: 10 })
"".upcase.downcase
RUBY

assert_equal("String", @type_inferrer.infer_receiver_type(node_context).name)

node_context = index_and_locate(<<~RUBY, { line: 0, character: 10 })
"".count.to_s
RUBY

assert_equal("Integer", @type_inferrer.infer_receiver_type(node_context).name)
end

def test_infer_symbol_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 5 })
:foo.to_s
Expand Down

0 comments on commit 88f2cbb

Please sign in to comment.