Skip to content

Commit

Permalink
added support for type inference of class variables
Browse files Browse the repository at this point in the history
  • Loading branch information
rogancodes committed Dec 17, 2024
1 parent f48298a commit 80a7595
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 3 deletions.
26 changes: 23 additions & 3 deletions lib/ruby_lsp/type_inferrer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ def infer_receiver_type(node_context)
infer_receiver_for_call_node(node, node_context)
when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
Prism::SuperNode, Prism::ForwardingSuperNode, Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode,
Prism::ClassVariableOperatorWriteNode, Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode,
Prism::ClassVariableTargetNode
Prism::SuperNode, Prism::ForwardingSuperNode
self_receiver_handling(node_context)
when Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode,
Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode,Prism::ClassVariableTargetNode
infer_receiver_for_class_variables(node_context)
end
end

Expand Down Expand Up @@ -145,6 +146,25 @@ def constant_name(node)
nil
end

sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
def infer_receiver_for_class_variables(node_context)
nesting_parts = node_context.nesting

return Type.new("Object") if nesting_parts.empty?

nesting_parts.reverse_each do |part|
break unless part.include?("<Class:")

nesting_parts.pop
end

receiver_name = nesting_parts.join("::")
resolved_receiver = @index.resolve(receiver_name, node_context.nesting)&.first
return unless resolved_receiver&.name

Type.new(resolved_receiver.name)
end

# A known type
class Type
extend T::Sig
Expand Down
68 changes: 68 additions & 0 deletions test/type_inferrer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,74 @@ def self.foo
assert_equal("Admin::User::<Class:User>", @type_inferrer.infer_receiver_type(node_context).name)
end

def test_infer_receiver_type_class_variables_in_class_body
node_context = index_and_locate(<<~RUBY, { line: 1, character: 2 })
class Foo
@@hello1
end
RUBY

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

def test_infer_receiver_type_class_variables_in_singleton_method
node_context = index_and_locate(<<~RUBY, { line: 2, character: 4 })
class Foo
def self.bar
@@hello1
end
end
RUBY

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

def test_infer_receiver_type_class_variables_in_singleton_block_body
node_context = index_and_locate(<<~RUBY, { line: 2, character: 4 })
class Foo
class << self
@@hello1
end
end
RUBY

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

def test_infer_receiver_type_class_variables_in_singleton_block_method
node_context = index_and_locate(<<~RUBY, { line: 3, character: 6 })
class Foo
class << self
def bar
@@hello1
end
end
end
RUBY

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

def test_infer_receiver_type_class_variables_in_instance_method
node_context = index_and_locate(<<~RUBY, { line: 2, character: 4 })
class Foo
def bar
@@hello1
end
end
RUBY

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

def test_infer_top_level_class_variables
node_context = index_and_locate(<<~RUBY, { line: 0, character: 0 })
@@foo
RUBY

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

private

def index_and_locate(source, position)
Expand Down

0 comments on commit 80a7595

Please sign in to comment.