mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
fix: reject ambiguous Ruby constant mutations
(cherry picked from commit 891ebfae53)
This commit is contained in:
1 parent
dd0b95d18a
commit
ff75ca1029
2 files changed
+84
-2
No files matched your search
@@ -3208,6 +3208,20 @@ def _ruby_external_method_owners(root_node, source: bytes) -> list[str]:
|
||||
owners: set[str] = set()
|
||||
|
||||
def visit(node) -> None:
|
||||
if node.type == "assignment":
|
||||
left = node.child_by_field_name("left")
|
||||
right = node.child_by_field_name("right")
|
||||
if (
|
||||
left is not None
|
||||
and left.type in {"constant", "scope_resolution"}
|
||||
and right is not None
|
||||
and right.type in {"constant", "scope_resolution"}
|
||||
):
|
||||
raw = _ruby_const_full_name(left, source)
|
||||
if raw:
|
||||
# Constant aliases can change which superclass an
|
||||
# unqualified reference denotes without changing its text.
|
||||
owners.add(raw)
|
||||
if node.type in {"class", "module"}:
|
||||
name = node.child_by_field_name("name")
|
||||
if name is not None and name.type == "scope_resolution":
|
||||
@@ -3230,7 +3244,7 @@ def _ruby_external_method_owners(root_node, source: bytes) -> list[str]:
|
||||
raw = _ruby_const_full_name(owner, source)
|
||||
if raw:
|
||||
owners.add(raw)
|
||||
return
|
||||
return
|
||||
if node.type == "call":
|
||||
receiver = node.child_by_field_name("receiver")
|
||||
method = node.child_by_field_name("method")
|
||||
@@ -3659,6 +3673,9 @@ def _extract_generic(
|
||||
if not name_node:
|
||||
return
|
||||
class_name = _read_text(name_node, source)
|
||||
ruby_absolute_declaration = (
|
||||
config.ts_module == "tree_sitter_ruby" and class_name.startswith("::")
|
||||
)
|
||||
# Ruby: fully qualify the module/class label with its enclosing
|
||||
# scope, splitting compact `Foo::Bar` names into segments so both
|
||||
# declaration styles converge on one `Foo::Bar` label (#2302).
|
||||
@@ -3693,11 +3710,13 @@ def _extract_generic(
|
||||
metadata["is_partial"] = True
|
||||
if config.ts_module == "tree_sitter_ruby":
|
||||
ruby_body = _find_body(node, config)
|
||||
if ruby_reopened or (
|
||||
if ruby_absolute_declaration or ruby_reopened or (
|
||||
ruby_body is not None
|
||||
and _ruby_body_has_lookup_barrier(ruby_body, source)
|
||||
):
|
||||
metadata = dict(metadata or {})
|
||||
if ruby_absolute_declaration:
|
||||
metadata["ruby_lookup_unsafe"] = True
|
||||
if ruby_reopened:
|
||||
metadata["ruby_reopened"] = True
|
||||
if ruby_body is not None and _ruby_body_has_lookup_barrier(
|
||||
|
||||
@@ -759,6 +759,27 @@ def test_inherited_constant_lookup_does_not_fall_back_past_lexical_owner(
|
||||
assert _only_call(graph)["confidence"] == "INFERRED"
|
||||
|
||||
|
||||
def test_inherited_call_stays_inferred_for_superclass_constant_alias(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
_write(tmp_path, "base.rb", "class Base\n def self.helper; :wrong; end\nend\n")
|
||||
_write(
|
||||
tmp_path,
|
||||
"other.rb",
|
||||
"class Other\n class << self\n attr_reader :helper\n end\nend\n",
|
||||
)
|
||||
_write(
|
||||
tmp_path,
|
||||
"child.rb",
|
||||
"Base = Other\nclass Child < Base\n"
|
||||
" def self.call\n helper()\n end\nend\n",
|
||||
)
|
||||
|
||||
graph = extract(sorted(tmp_path.glob("*.rb")), cache_root=tmp_path, parallel=False)
|
||||
|
||||
assert _only_call(graph)["confidence"] == "INFERRED"
|
||||
|
||||
|
||||
def test_inherited_call_stays_inferred_under_enclosing_refinement(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
@@ -821,6 +842,27 @@ def test_inherited_call_stays_inferred_for_external_owner_eval(
|
||||
assert _only_call(graph)["confidence"] == "INFERRED"
|
||||
|
||||
|
||||
def test_external_owner_mutator_inside_singleton_class_stays_inferred(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
_write(tmp_path, "base.rb", "class Base\n def self.helper; :base; end\nend\n")
|
||||
_write(
|
||||
tmp_path,
|
||||
"child.rb",
|
||||
"class Child < Base\n def self.call\n helper()\n end\nend\n",
|
||||
)
|
||||
_write(
|
||||
tmp_path,
|
||||
"override.rb",
|
||||
"module Installer\n class << self\n"
|
||||
" Child.define_singleton_method(:helper) { :child }\n end\nend\n",
|
||||
)
|
||||
|
||||
graph = extract(sorted(tmp_path.glob("*.rb")), cache_root=tmp_path, parallel=False)
|
||||
|
||||
assert _only_call(graph)["confidence"] == "INFERRED"
|
||||
|
||||
|
||||
def test_inherited_call_stays_inferred_for_compact_reopening(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
@@ -843,6 +885,27 @@ def test_inherited_call_stays_inferred_for_compact_reopening(
|
||||
assert _only_call(graph)["confidence"] == "INFERRED"
|
||||
|
||||
|
||||
def test_absolute_class_declaration_inside_namespace_stays_inferred(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
_write(tmp_path, "base.rb", "class Base\n def helper; :base; end\nend\n")
|
||||
_write(
|
||||
tmp_path,
|
||||
"child.rb",
|
||||
"module Wrapper\n class ::Child < ::Base\n"
|
||||
" def call\n helper()\n end\n end\nend\n",
|
||||
)
|
||||
_write(
|
||||
tmp_path,
|
||||
"override.rb",
|
||||
"class Child\n attr_reader :helper\nend\n",
|
||||
)
|
||||
|
||||
graph = extract(sorted(tmp_path.glob("*.rb")), cache_root=tmp_path, parallel=False)
|
||||
|
||||
assert _only_call(graph)["confidence"] == "INFERRED"
|
||||
|
||||
|
||||
def test_inherited_call_stops_at_ambiguous_nearer_method(tmp_path: Path) -> None:
|
||||
_write(tmp_path, "grand.rb", "class Grand\n def helper; :grand; end\nend\n")
|
||||
_write(
|
||||
|
||||
Reference in new issue
Block a user