Skip to content

Commit

Permalink
Add literal types to inferrer (#2468)
Browse files Browse the repository at this point in the history
* Only show guessed type information if the type is indeed guessed

* Add literal types to inferrer
  • Loading branch information
vinistock authored Aug 21, 2024
1 parent 0d919fb commit 8e3dbfc
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/ruby_lsp/listeners/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def complete_methods(node, name)

return unless range

guessed_type = type.name
guessed_type = type.is_a?(TypeInferrer::GuessedType) && type.name

@index.method_completion_candidates(method_name, type.name).each do |entry|
entry_name = entry.name
Expand Down
38 changes: 38 additions & 0 deletions lib/ruby_lsp/type_inferrer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,47 @@ def infer_receiver_type(node_context)
def infer_receiver_for_call_node(node, node_context)
receiver = node.receiver

# For receivers inside parenthesis, such as ranges like (0...2), we need to unwrap the parenthesis to get the
# actual node
if receiver.is_a?(Prism::ParenthesesNode)
statements = receiver.body

if statements.is_a?(Prism::StatementsNode)
body = statements.body

if body.length == 1
receiver = body.first
end
end
end

case receiver
when Prism::SelfNode, nil
self_receiver_handling(node_context)
when Prism::StringNode
Type.new("String")
when Prism::SymbolNode
Type.new("Symbol")
when Prism::ArrayNode
Type.new("Array")
when Prism::HashNode
Type.new("Hash")
when Prism::IntegerNode
Type.new("Integer")
when Prism::FloatNode
Type.new("Float")
when Prism::RegularExpressionNode
Type.new("Regexp")
when Prism::NilNode
Type.new("NilClass")
when Prism::TrueNode
Type.new("TrueClass")
when Prism::FalseNode
Type.new("FalseClass")
when Prism::RangeNode
Type.new("Range")
when Prism::LambdaNode
Type.new("Proc")
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 Down
21 changes: 21 additions & 0 deletions test/requests/completion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,27 @@ class Foo
end
end

def test_guessed_type_name_is_only_included_for_guessed_types
source = +<<~RUBY
[].e
RUBY

with_server(source) do |server, uri|
index = server.instance_variable_get(:@global_state).index
RubyIndexer::RBSIndexer.new(index).index_ruby_core

server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 0, character: 4 },
})

items = server.pop_response.response
items.each do |item|
refute(item.data[:guessed_type])
end
end
end

private

def with_file_structure(server, &block)
Expand Down
96 changes: 96 additions & 0 deletions test/type_inferrer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,102 @@ def initialize(a, b, c)
assert_equal("Foo", @type_inferrer.infer_receiver_type(node_context).name)
end

def test_infer_string_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 3 })
"".upcase
RUBY

assert_equal("String", @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
RUBY

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

def test_infer_array_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 3 })
[].first
RUBY

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

def test_infer_hash_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 3 })
{}.keys
RUBY

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

def test_infer_integer_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 3 })
10.to_s
RUBY

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

def test_infer_float_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 4 })
1.5.to_s
RUBY

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

def test_infer_regexp_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 5 })
/abc/.match("abc")
RUBY

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

def test_infer_nil_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 4 })
nil.to_s
RUBY

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

def test_infer_true_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 5 })
true.to_s
RUBY

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

def test_infer_false_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 6 })
false.to_s
RUBY

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

def test_infer_range_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 8 })
(5..10).to_a
RUBY

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

def test_infer_lambda_literal
node_context = index_and_locate(<<~RUBY, { line: 0, character: 5 })
->{}.call
RUBY

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

private

def index_and_locate(source, position)
Expand Down

0 comments on commit 8e3dbfc

Please sign in to comment.