fix(csharp): don't collect tuple element names as type references (#3796)

A named tuple type `(int mode, string label)` is a `tuple_type` whose
`tuple_element` children carry both a `type` and a `name` field.
_csharp_collect_type_refs walked every named child, so the element NAMES
were collected as type references, minted as sourceless placeholder nodes
and emitted `references` edges — one junk type node per element, repeated
in every file declaring such a signature.

Only the `type` field of each tuple element should feed type-reference
collection; the `name` is an identifier, not a type reference. Fix the
collector to descend into the element's type field only.

Regression: tests/test_csharp_tuple_type_refs.py asserts no node/edge is
minted for element names (all existing C# tests pass).

(cherry picked from commit bfcdda97b0)
This commit is contained in:
KaiyiQuan authored and safishamsi committed 2026-09-27 18:39:52 +01:00
1 parent 286000d199
commit 3c407dcb8c
2 files changed
+63

No files matched your search

+11
View File
@@ -235,6 +235,17 @@ def _csharp_collect_type_refs(
if c.is_named:
_csharp_collect_type_refs(c, source, generic, out, skip)
return
if t == "tuple_type":
# A named tuple element carries both a `type` and a `name` field
# (`(int mode, string label)`). Only the `type` field feeds
# type-reference collection; the `name` is an identifier, not a type
# reference, and minting it produces junk "type" nodes (#3796).
for el in node.children:
if el.type == "tuple_element":
type_child = el.child_by_field_name("type")
if type_child is not None:
_csharp_collect_type_refs(type_child, source, generic, out, skip)
return
if node.is_named:
for c in node.children:
if c.is_named:
+52
View File
@@ -0,0 +1,52 @@
"""C# named-tuple element names must not be collected as type references (#3796).
In tree-sitter-c-sharp a named tuple type `(int mode, string label)` is a
`tuple_type` whose children are `tuple_element` nodes carrying a `type` field
and a `name` field. The type-reference collector walks every named child, so
the element NAMES (`mode`, `label`) were minted as sourceless "type" nodes and
emitted `references` edges from the declaring member — one junk type node per
element, repeated in every file that declares such a signature. Only the
`type` field of each element should feed type-reference collection.
"""
from graphify.extract import extract
READER_CS = (
"namespace Demo\n{\n public class Reader\n {\n"
" public static (int mode, string label) Read() => (1, \"a\");\n"
" }\n}\n"
)
def _extract_source(source: str):
import os
import tempfile
from pathlib import Path
d = Path(tempfile.mkdtemp())
p = d / "Reader.cs"
p.write_text(source)
old = os.getcwd()
try:
os.chdir(d)
return extract([Path("Reader.cs")], cache_root=Path(tempfile.mkdtemp()))
finally:
os.chdir(old)
def test_tuple_element_names_are_not_type_references():
r = _extract_source(READER_CS)
# Element NAMES must not be minted as type nodes.
labels = {n["label"] for n in r["nodes"]}
assert "mode" not in labels
assert "label" not in labels
# And no references edge may target a placeholder minted from a name.
for e in r["edges"]:
if e["relation"] != "references":
continue
target = next(n for n in r["nodes"] if n["id"] == e["target"])
assert target["label"] not in ("mode", "label"), (
f"references edge to tuple element name {target['label']}"
)