Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/codegraphcontext/tools/scip_indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ def parse(self, index_scip_path: Path, project_path: Path) -> Dict[str, Any]:
except Exception as e:
error_logger(f"Failed to import codegraphcontext.tools.scip_pb2: {e}")
return {}
Kind = scip_pb2.SymbolInformation

try:
with open(index_scip_path, "rb") as f:
Expand Down Expand Up @@ -577,7 +578,7 @@ def parse(self, index_scip_path: Path, project_path: Path) -> Dict[str, Any]:
if 0 < line_num <= len(source):
src_line = source[line_num - 1].strip().lower()
if src_line.startswith("interface "):
kind = 20 # Interface
kind = Kind.Interface
elif src_line.startswith("trait "):
kind = 53 # Trait
info["kind"] = kind
Expand All @@ -587,7 +588,9 @@ def parse(self, index_scip_path: Path, project_path: Path) -> Dict[str, Any]:
# Apply Rust implementations if available
rust_impls = symbol_def_table.get("rust_impls", {})
name = self._name_from_symbol(sym)
if name in rust_impls and info.get("kind") in (7, 18, 20, 49, 53, 54):
if name in rust_impls and info.get("kind") in (
Kind.Class, Kind.Enum, Kind.Interface, Kind.Struct, Kind.Trait, Kind.Protocol
):
info["bases"] = list(set(info.get("bases", []) + list(rust_impls[name])))

for doc in index.documents:
Expand Down Expand Up @@ -632,7 +635,7 @@ def parse(self, index_scip_path: Path, project_path: Path) -> Dict[str, Any]:
src = doc_source_lines.get(rel_path, [])
src_line = src[line - 1].strip().lower() if 0 < line <= len(src) else ""
if src_line.startswith("interface "):
kind = 20
kind = Kind.Interface
elif src_line.startswith("trait "):
kind = 53
elif sym.startswith("scip-go") or sym.startswith("rust-analyzer"):
Expand All @@ -657,12 +660,12 @@ def parse(self, index_scip_path: Path, project_path: Path) -> Dict[str, Any]:
node["bases"] = [self._name_from_symbol(b) for b in defn.get("bases", [])]
node["context"] = None
file_data["classes"].append(node)
elif kind == 20 or kind == 54: # Interface or Protocol
elif kind in (Kind.Interface, Kind.Protocol):
node["bases"] = [self._name_from_symbol(b) for b in defn.get("bases", [])]
node["context"] = None
if "interfaces" not in file_data: file_data["interfaces"] = []
file_data["interfaces"].append(node)
elif kind == 18: # Enum
elif kind == Kind.Enum:
node["context"] = None
if "enums" not in file_data: file_data["enums"] = []
file_data["enums"].append(node)
Expand Down Expand Up @@ -756,7 +759,8 @@ def _infer_cxx_zero_kind(self, sym: str, line: int, source_lines: List[str]) ->
if 0 < line <= len(source_lines):
src_line = source_lines[line - 1].strip().lower()
if "enum " in src_line or src_line.startswith("enum"):
return 18
from . import scip_pb2
return scip_pb2.SymbolInformation.Enum
if src_line.startswith("union "):
return 49
if src_line.startswith("struct "):
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/tools/test_scip_indexer_c_family.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def test_infer_cxx_enum_vs_struct_vs_class() -> None:
"union MyUnion {",
"class Foo {",
]
assert p._infer_cxx_zero_kind("cxx . . $ Color#", 1, lines) == 18
assert p._infer_cxx_zero_kind("cxx . . $ Direction#", 2, lines) == 18
assert p._infer_cxx_zero_kind("cxx . . $ Color#", 1, lines) == 11
assert p._infer_cxx_zero_kind("cxx . . $ Direction#", 2, lines) == 11
assert p._infer_cxx_zero_kind("cxx . . $ MyStruct#", 3, lines) == 49
assert p._infer_cxx_zero_kind("cxx . . $ MyUnion#", 4, lines) == 49
assert p._infer_cxx_zero_kind("cxx . . $ Foo#", 5, lines) == 7
Expand Down
76 changes: 76 additions & 0 deletions tests/unit/tools/test_scip_modern_kinds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""ScipIndexParser routes definitions by SymbolInformation.kind, using the numbers
of the SCIP schema: Enum 11, Interface 21, Protocol 42. Getter 18, Instance 20 and
Type 54 are different kinds and must not be taken for Enum / Interface / Protocol.
Kinds the parser infers for producers that report 0 use the same numbers.
"""

from pathlib import Path

import pytest

from codegraphcontext.tools import scip_pb2
from codegraphcontext.tools.scip_indexer import ScipIndexParser


def _parse(tmp_path: Path, symbol: str, kind: int, source: str) -> dict:
index = scip_pb2.Index()
doc = index.documents.add()
doc.relative_path = "Sample.src"
definition = doc.occurrences.add()
definition.symbol = symbol
definition.symbol_roles = 1 # Definition
definition.range.extend([0, 0, 1])
sym_info = doc.symbols.add()
sym_info.symbol = symbol
sym_info.kind = kind
scip_path = tmp_path / "index.scip"
scip_path.write_bytes(index.SerializeToString())
(tmp_path / "Sample.src").write_text(source)
return ScipIndexParser().parse(scip_path, tmp_path)["files"][str((tmp_path / "Sample.src").resolve())]


def _buckets(file_data: dict) -> list:
return sorted(
b for b in ("classes", "interfaces", "structs", "enums", "traits", "functions", "variables") if file_data.get(b)
)


@pytest.mark.parametrize(
"kind, symbol, bucket",
[
(scip_pb2.SymbolInformation.Enum, "pkg . . sample/Color#", "enums"),
(scip_pb2.SymbolInformation.Interface, "pkg . . sample/Reader#", "interfaces"),
(scip_pb2.SymbolInformation.Protocol, "pkg . . sample/Drawable#", "interfaces"),
(scip_pb2.SymbolInformation.Class, "pkg . . sample/Widget#", "classes"),
(scip_pb2.SymbolInformation.Struct, "pkg . . sample/Point#", "structs"),
(scip_pb2.SymbolInformation.Trait, "pkg . . sample/Show#", "traits"),
(scip_pb2.SymbolInformation.Function, "pkg . . sample/make().", "functions"),
(scip_pb2.SymbolInformation.Method, "pkg . . sample/Point#read().", "functions"),
(scip_pb2.SymbolInformation.Field, "pkg . . sample/Point#name.", "variables"),
(scip_pb2.SymbolInformation.Variable, "pkg . . sample/count.", "variables"),
],
)
def test_producer_kind_routes_to_its_bucket(tmp_path: Path, kind: int, symbol: str, bucket: str) -> None:
assert _buckets(_parse(tmp_path, symbol, kind, "x\n")) == [bucket]


@pytest.mark.parametrize(
"kind, not_bucket",
[
(scip_pb2.SymbolInformation.Getter, "enums"),
(scip_pb2.SymbolInformation.Instance, "interfaces"),
(scip_pb2.SymbolInformation.Type, "interfaces"),
],
)
def test_other_kinds_are_not_taken_for_enum_interface_or_protocol(tmp_path: Path, kind: int, not_bucket: str) -> None:
assert not_bucket not in _buckets(_parse(tmp_path, "pkg . . sample/Thing#", kind, "x\n"))


def test_zero_kind_interface_from_source_is_an_interface(tmp_path: Path) -> None:
file_data = _parse(tmp_path, "scip-php composer . . sample/IFoo#", 0, "interface IFoo\n")
assert _buckets(file_data) == ["interfaces"]


def test_zero_kind_cxx_enum_is_an_enum(tmp_path: Path) -> None:
file_data = _parse(tmp_path, "cxx . . $ Color#", 0, "enum Color {\n")
assert _buckets(file_data) == ["enums"]