fix(go): don't emit embeds for interface type-set constraints (#1818)

This commit is contained in:
Synvoya authored and safishamsi committed 2026-08-30 15:53:16 +01:00
1 parent 9aeade7199
commit 588dc041f7
2 files changed
+55 -1

No files matched your search

+14 -1
View File
@@ -372,6 +372,16 @@ def extract_go(path: Path) -> dict:
for elem in type_body.children:
if elem.type != "type_elem":
continue
# A type_elem that is a generics type-set constraint -
# a union (`A | B`) or an approximation (`~T`) - is NOT
# interface embedding. Go only embeds a lone interface
# type; union/approximation terms can never be embedded,
# so they must not emit `embeds` heritage edges. Keep the
# type link as a `references` (type_constraint) edge.
is_type_set = any(
c.type == "|" or c.type == "negated_type"
for c in elem.children
)
refs = []
for sub in elem.children:
if sub.is_named:
@@ -380,7 +390,10 @@ def extract_go(path: Path) -> dict:
tgt = ensure_named_node(ref_name, elem.start_point[0] + 1)
if tgt == type_nid:
continue
if role == "type":
if is_type_set:
add_edge(type_nid, tgt, "references",
elem.start_point[0] + 1, context="type_constraint")
elif role == "type":
add_edge(type_nid, tgt, "embeds",
elem.start_point[0] + 1)
else:
+41
View File
@@ -1997,6 +1997,47 @@ def test_go_receiver_uses_pkg_scope():
assert "sample" not in server_nodes[0]["id"].split(":")[0]
def test_go_interface_embedding_emits_embeds():
"""`type ReaderLogger interface { Logger; Reader }` embeds both interfaces."""
r = extract_go(FIXTURES / "sample.go")
embeds = _edge_labels(r, "embeds")
assert ("ReaderLogger", "Logger") in embeds
assert ("ReaderLogger", "Reader") in embeds
def test_go_struct_embedding_emits_embeds():
"""`type DataProcessor struct { BaseProcessor }` embeds the base struct."""
r = extract_go(FIXTURES / "sample.go")
assert ("DataProcessor", "BaseProcessor") in _edge_labels(r, "embeds")
def test_go_interface_type_union_is_not_embedding(tmp_path):
"""A generics type-set constraint (`A | B`) is NOT interface embedding.
Go only allows *interfaces* to be embedded, and each embed is its own
element. A union of type terms (`MyInt | MyFloat`) is a constraint type
set that can never be embedded, so its terms must not emit `embeds`
heritage edges. They are reclassified as `references` (type_constraint)
so the type link is preserved without asserting false composition.
"""
source = tmp_path / "constraint.go"
source.write_text(
"package p\n"
"type MyInt int\n"
"type MyFloat float64\n"
"type Number interface {\n"
"\tMyInt | MyFloat\n"
"}\n"
)
r = extract_go(source)
embeds = _edge_labels(r, "embeds")
assert ("Number", "MyInt") not in embeds
assert ("Number", "MyFloat") not in embeds
refs = _edge_labels(r, "references", "type_constraint")
assert ("Number", "MyInt") in refs
assert ("Number", "MyFloat") in refs
# ---------------------------------------------------------------------------
# Julia
# ---------------------------------------------------------------------------