Ctrl K
ComunidadGuías…

tests/test_go_same_file_receiver_calls.py

Rama v8 · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""同文件 Go 调用必须区分接收者方法与包级函数。"""
from __future__ import annotations

import importlib.util

import pytest

from graphify.extract import extract

pytestmark = pytest.mark.skipif(
    importlib.util.find_spec("tree_sitter_go") is None,
    reason="tree_sitter_go not installed",
)


def _targets(tmp_path, source, caller="Use()", siblings=None):
    path = tmp_path / "app.go"
    path.write_text("package demo\n" + source, encoding="utf-8")
    paths = [path]
    for name, body in (siblings or {}).items():
        sibling = tmp_path / name
        sibling.write_text("package demo\n" + body, encoding="utf-8")
        paths.append(sibling)
    result = extract(paths, root=tmp_path, cache_root=tmp_path / "cache", parallel=False)
    nodes = {node["id"]: node for node in result["nodes"]}
    owners = {
        edge["target"]: nodes[edge["source"]]["label"]
        for edge in result["edges"]
        if edge["relation"] == "method"
    }
    return [
        (owners.get(edge["target"]), nodes[edge["target"]]["label"], edge["confidence"])
        for edge in result["edges"]
        if edge["relation"] == "calls" and nodes[edge["source"]]["label"] == caller
    ]


@pytest.mark.parametrize("reverse", [False, True])
def test_same_named_methods_follow_the_receiver_in_either_declaration_order(tmp_path, reverse):
    methods = ["func (*A) apply() {}\n", "func (*B) apply() {}\n"]
    if reverse:
        methods.reverse()
    source = "type A struct{}\ntype B struct{}\n" + "".join(methods)
    source += "func Use(a *A) { a.apply() }\n"
    assert _targets(tmp_path, source) == [("A", ".apply()", "INFERRED")]


def test_a_unique_method_on_another_type_does_not_answer_the_receiver(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*B) apply() {}\n"
        "func Use(a *A) { a.apply() }\n"
    )
    assert _targets(tmp_path, source) == []


def test_an_unknown_receiver_does_not_bind_a_unique_local_method(tmp_path):
    source = (
        "type B struct{}\nfunc (*B) apply() {}\n"
        "func Use(a Missing) { a.apply() }\n"
    )
    assert _targets(tmp_path, source) == []


@pytest.mark.parametrize("reverse", [False, True])
def test_a_bare_function_call_never_selects_a_same_named_method(tmp_path, reverse):
    declarations = ["func apply() {}\n", "func (*B) apply() {}\n"]
    if reverse:
        declarations.reverse()
    source = "type B struct{}\n" + "".join(declarations) + "func Use() { apply() }\n"
    assert _targets(tmp_path, source) == [(None, "apply()", "EXTRACTED")]


def test_an_imported_selector_never_selects_a_local_function(tmp_path):
    source = 'import "fmt"\nfunc Println() {}\nfunc Use() { fmt.Println() }\n'
    assert _targets(tmp_path, source) == []


def test_each_function_keeps_its_receiver_type_in_the_same_file(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func Other(a *B) { a.apply() }\n"
        "func Use(a *A) { a.apply() }\n"
    )
    assert _targets(tmp_path, source) == [("A", ".apply()", "INFERRED")]


def test_a_field_on_the_methods_own_receiver_selects_its_declared_type(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\ntype App struct { value *A }\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func (app *App) Use() { app.value.apply() }\n"
    )
    assert _targets(tmp_path, source, caller=".Use()") == [("A", ".apply()", "INFERRED")]


def test_an_unknown_local_does_not_borrow_another_functions_parameter_type(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func Other(x *A) {}\nfunc NewB() *B { return &B{} }\n"
        "func Use() { x := NewB(); x.apply() }\n"
    )
    assert _targets(tmp_path, source) == [(None, "NewB()", "EXTRACTED")]


@pytest.mark.parametrize("binding", ["var x *B", "var x = &B{}"])
def test_a_package_variable_does_not_borrow_a_functions_parameter_type(tmp_path, binding):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func Other(x *A) {}\n" + binding + "\nfunc Use() { x.apply() }\n"
    )
    assert _targets(tmp_path, source) == [("B", ".apply()", "INFERRED")]


@pytest.mark.parametrize("binding", ["x := NewB()", "var x = NewB()"])
def test_an_unknown_local_shadows_a_typed_package_variable(tmp_path, binding):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "var x *A\nfunc NewB() *B { return &B{} }\n"
        "func Use() { " + binding + "; x.apply() }\n"
    )
    assert _targets(tmp_path, source) == [(None, "NewB()", "EXTRACTED")]


def test_a_qualified_parameter_does_not_borrow_a_local_type(tmp_path):
    source = (
        'import "example.org/other"\n'
        "type A struct{}\nfunc (*A) apply() {}\n"
        "func Other(x *A) {}\nfunc Use(x *other.A) { x.apply() }\n"
    )
    assert _targets(tmp_path, source) == []


def test_a_field_owner_beats_another_structs_same_named_field(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "type Other struct { value *A }\ntype App struct { value *B }\n"
        "func (s *App) Use() { s.value.apply() }\n"
    )
    assert _targets(tmp_path, source, caller=".Use()") == [("B", ".apply()", "INFERRED")]


@pytest.mark.parametrize("reverse", [False, True])
def test_a_direct_call_takes_priority_over_a_method_value(tmp_path, reverse):
    statements = ["Register(a.apply)", "a.apply()"]
    if reverse:
        statements.reverse()
    source = (
        "type A struct{}\nfunc (*A) apply() {}\nfunc Register(f func()) {}\n"
        "func Use(a *A) { " + "; ".join(statements) + " }\n"
    )
    assert set(_targets(tmp_path, source)) == {
        (None, "Register()", "EXTRACTED"), ("A", ".apply()", "INFERRED"),
    }


@pytest.mark.parametrize("expression", ["T.apply(t)", "(*T).apply(&t)"])
def test_an_explicit_method_expression_resolves_its_receiver_type(tmp_path, expression):
    source = (
        "type T struct{}\nfunc (T) apply() {}\n"
        "func Use(t T) { " + expression + " }\n"
    )
    assert _targets(tmp_path, source) == [("T", ".apply()", "INFERRED")]


@pytest.mark.parametrize("binding", ["", "x := &Box[int]{}; ", "var x *Box[int]; "])
def test_a_generic_receiver_keeps_its_base_type(tmp_path, binding):
    source = (
        "type Box[T any] struct{}\nfunc (*Box[T]) apply() {}\n"
        + ("func Use(x *Box[int]) { x.apply() }\n" if not binding
           else "func Use() { " + binding + "x.apply() }\n")
    )
    assert _targets(tmp_path, source) == [("Box", ".apply()", "INFERRED")]


@pytest.mark.parametrize("outer_call", [False, True])
def test_a_block_binding_shadows_its_parameter_and_restores_it_afterward(tmp_path, outer_call):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func Use(x *A) { { x := &B{}; x.apply() }; "
        + ("x.apply()" if outer_call else "") + " }\n"
    )
    expected = {("B", ".apply()", "INFERRED")}
    if outer_call:
        expected.add(("A", ".apply()", "INFERRED"))
    assert set(_targets(tmp_path, source)) == expected


@pytest.mark.parametrize("outer_call", [False, True])
def test_an_unknown_block_binding_cannot_borrow_its_outer_type(tmp_path, outer_call):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func NewB() *B { return &B{} }\n"
        "func Use(x *A) { { x := NewB(); x.apply() }; "
        + ("x.apply()" if outer_call else "") + " }\n"
    )
    expected: set[tuple[str | None, str, str]] = {(None, "NewB()", "EXTRACTED")}
    if outer_call:
        expected.add(("A", ".apply()", "INFERRED"))
    assert set(_targets(tmp_path, source)) == expected


def test_a_range_binding_with_unknown_element_type_shadows_its_parameter(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func Use(x *A, xs []*B) { for _, x := range xs { x.apply() } }\n"
    )
    assert _targets(tmp_path, source) == []


@pytest.mark.parametrize("binding", ["var x *B", "var x = &B{}", "x := &B{}"])
@pytest.mark.parametrize("before", [False, True])
def test_a_local_binding_only_applies_after_its_declaration(tmp_path, binding, before):
    statements = "x.apply(); " + binding if before else binding + "; x.apply()"
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "var x *A\nfunc Use() { " + statements + " }\n"
    )
    assert _targets(tmp_path, source) == [
        ("A" if before else "B", ".apply()", "INFERRED"),
    ]


@pytest.mark.parametrize("unknown", [False, True])
@pytest.mark.parametrize("outer_call", [False, True])
def test_an_if_initializer_binding_stays_inside_the_if(tmp_path, unknown, outer_call):
    binding = "NewB()" if unknown else "&B{}"
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func NewB() *B { return &B{} }\n"
        "func Use(x *A) { if x := " + binding + "; x != nil { x.apply() }; "
        + ("x.apply()" if outer_call else "") + " }\n"
    )
    expected: set[tuple[str | None, str, str]] = {(None, "NewB()", "EXTRACTED")} if unknown else {
        ("B", ".apply()", "INFERRED"),
    }
    if outer_call:
        expected.add(("A", ".apply()", "INFERRED"))
    assert set(_targets(tmp_path, source)) == expected


def test_a_range_binding_does_not_escape_the_loop(tmp_path):
    source = (
        "type A struct{}\ntype B struct{}\n"
        "func (*A) apply() {}\nfunc (*B) apply() {}\n"
        "func Use(x *A, xs []*B) { for _, x := range xs { x.apply() }; x.apply() }\n"
    )
    assert _targets(tmp_path, source) == [("A", ".apply()", "INFERRED")]


def test_a_bare_call_resolves_a_sibling_function_instead_of_a_local_method(tmp_path):
    source = "type B struct{}\nfunc (B) apply() {}\nfunc Use() { apply() }\n"
    assert _targets(tmp_path, source, siblings={"other.go": "func apply() {}\n"}) == [
        (None, "apply()", "INFERRED"),
    ]