Skip to content

Fix type inference for parent scopes and method definitions with receivers#4062

Open
vinistock wants to merge 3 commits intorubydex_adoption_feature_branchfrom
vs_fix_node_context_for_methods_with_receivers
Open

Fix type inference for parent scopes and method definitions with receivers#4062
vinistock wants to merge 3 commits intorubydex_adoption_feature_branchfrom
vs_fix_node_context_for_methods_with_receivers

Conversation

@vinistock
Copy link
Copy Markdown
Member

Motivation

In the original Ruby LSP indexer, we did not handle the resolution of parent scopes used in definitions. For example:

module Bar; end

module Foo
  # This defines `Bar::Baz` because the `Bar` reference gets resolved to top level `Bar`
  # through the lexical scope. Rubydex does this correctly, but the Ruby LSP doesn't
  # run resolution on its node context and misunderstands this as `Foo::Bar::Baz`
  class Bar::Baz
  end
end

Similarly this example

module Foo
  module Bar; end
end

module Baz
  include Foo

  # This doesn't define `Baz::Bar::Qux`. It has access to `Bar` through inheritance
  # and defines `Foo::Bar::Qux`.
  #
  # Note that references to `Baz::Bar::Qux` also work, but not because that's the fully
  # qualified name of the constant. `Baz::Bar` finds `Foo::Bar` through inheritance,
  # allowing you to refer to `Qux`
  class Bar::Qux; end
end

We also did not handle method definition receivers correctly, which don't advance the lexical scope, but modify the type of self. For example:

class Bar; end

class Foo
  def Bar.baz
    # self here is singleton class of Bar (Bar::<Bar>). Therefore, the var reference
    # is accessing `Bar::<Bar>#@var`
    @var
  end
end

Implementation

There are two main parts to fix, which can be found in the first commit:

  1. Started using a new object to represent the surrounding method definition in node context, which includes the receiver
  2. Stopped pushing def self.foo as a singleton entry in the node context nesting. Method receivers don't advance the lexical scope and do not influence constant resolution. They only influence the type of self

Then I made the necessary adjustments on the type inferrer.

Automated Tests

The last commit includes a bunch of tests verifying that we're correctly handling these cases in definition and hover.

@vinistock vinistock self-assigned this Apr 15, 2026
@vinistock vinistock added the bugfix This PR will fix an existing bug label Apr 15, 2026
@vinistock vinistock requested a review from a team as a code owner April 15, 2026 21:35
@vinistock vinistock added the server This pull request should be included in the server gem's release notes label Apr 15, 2026
@vinistock vinistock requested review from alexcrocha and st0012 April 15, 2026 21:35
@vinistock vinistock force-pushed the vs_fix_node_context_for_methods_with_receivers branch from 93eacb0 to 338eeee Compare April 17, 2026 13:22
@vinistock vinistock force-pushed the rubydex_adoption_feature_branch branch from 9bf9d19 to 9ca38db Compare April 21, 2026 15:02
@vinistock vinistock force-pushed the vs_fix_node_context_for_methods_with_receivers branch from 338eeee to 4c1f108 Compare April 21, 2026 15:02
@vinistock vinistock force-pushed the rubydex_adoption_feature_branch branch from 328b3db to d1435ac Compare April 28, 2026 14:34
@vinistock vinistock force-pushed the vs_fix_node_context_for_methods_with_receivers branch from 4c1f108 to 9846ab4 Compare April 28, 2026 14:34
assert_equal(6, response[0].range.start.line)
end
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a few tests using extend?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no logic in go to definition that's specific to extend and I've been trying to not mix Rubydex related tests with the request related tests.

For example, we already have tests to assert that we follow ancestors when searching for instance variable definitions. If that wasn't working for extend, that would be a bug in Rubydex and so I believe the test belongs there.

when Prism::ConstantReadNode, Prism::ConstantPathNode
MethodDef.new(node.name.to_s, receiver.slice)
else
MethodDef.new(node.name.to_s, nil)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we'll handle cases like def foo.bar; baz; end as if it didn't have a receiver?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. It's not correct, but I didn't think it was worth the complexity to add a special case to represent an unknown type of self.

Method definition receivers other than self are already not super common and dynamic ones are even more rare.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix This PR will fix an existing bug server This pull request should be included in the server gem's release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants