mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
fix: exclude non-types from C# type resolution
(cherry picked from commit dfe5a220b2)
This commit is contained in:
1 parent
cac084cf3d
commit
769851dfae
2 files changed
+157
-3
No files matched your search
@@ -16,8 +16,23 @@ from pathlib import Path
|
||||
from graphify.extractors.base import _make_id
|
||||
|
||||
|
||||
def _build_csharp_type_def_index(all_nodes: list[dict]) -> dict[tuple[str, str], str]:
|
||||
def _build_csharp_type_def_index(
|
||||
all_nodes: list[dict],
|
||||
all_edges: list[dict] | None = None,
|
||||
) -> dict[tuple[str, str], str]:
|
||||
"""Return deterministic ``(namespace, name) -> node_id`` C# type definitions."""
|
||||
contained: set[str] = set()
|
||||
non_type_ids: set[str] = set()
|
||||
if all_edges is not None:
|
||||
for edge in all_edges:
|
||||
rel = edge.get("relation")
|
||||
tgt = edge.get("target")
|
||||
if isinstance(tgt, str):
|
||||
if rel == "contains":
|
||||
contained.add(tgt)
|
||||
elif rel in ("case_of", "defines", "method"):
|
||||
non_type_ids.add(tgt)
|
||||
|
||||
candidates: dict[tuple[str, str], list[dict]] = {}
|
||||
for node in all_nodes:
|
||||
if node.get("type") == "namespace":
|
||||
@@ -31,6 +46,8 @@ def _build_csharp_type_def_index(all_nodes: list[dict]) -> dict[tuple[str, str],
|
||||
label = node.get("label")
|
||||
if not (isinstance(nid, str) and nid and isinstance(label, str) and label):
|
||||
continue
|
||||
if all_edges is not None and (nid not in contained or nid in non_type_ids):
|
||||
continue
|
||||
source_file = node.get("source_file")
|
||||
if (
|
||||
not isinstance(source_file, str)
|
||||
@@ -105,7 +122,7 @@ def _resolve_cross_file_csharp_imports(
|
||||
if isinstance(label, str) and label and isinstance(nid, str) and nid:
|
||||
namespace_id_by_label.setdefault(label, nid)
|
||||
|
||||
type_def_index = _build_csharp_type_def_index(all_nodes)
|
||||
type_def_index = _build_csharp_type_def_index(all_nodes, all_edges)
|
||||
if not namespace_id_by_label and not type_def_index:
|
||||
return
|
||||
|
||||
@@ -184,7 +201,7 @@ class CsharpNameResolver:
|
||||
for node in all_nodes
|
||||
if isinstance(node.get("id"), str) and node.get("id")
|
||||
}
|
||||
self.type_def_index = _build_csharp_type_def_index(all_nodes)
|
||||
self.type_def_index = _build_csharp_type_def_index(all_nodes, all_edges)
|
||||
self.known_namespaces = {
|
||||
node.get("label")
|
||||
for node in all_nodes
|
||||
|
||||
@@ -567,3 +567,140 @@ def test_csharp_alias_using_scoped_to_its_block(tmp_path: Path):
|
||||
inh = {(e["source"], e["target"]) for e in result["edges"] if e.get("relation") == "inherits"}
|
||||
assert (good["id"], n_t["id"]) in inh, "Good must bind N.T via the in-block alias"
|
||||
assert (bad["id"], n_t["id"]) not in inh, "Bad (sibling block) must NOT see the alias"
|
||||
|
||||
|
||||
def test_csharp_same_file_enum_member_does_not_preempt_class_inheritance(tmp_path: Path):
|
||||
# Repro A (#3795): enum member Follow must not take the inheritance edge from class Follow.
|
||||
f = _write(
|
||||
tmp_path / "effects.cs",
|
||||
"namespace Demo {\n"
|
||||
" public enum EffectKind { Tween, Follow }\n"
|
||||
" public abstract class Follow { }\n"
|
||||
" public sealed class FollowFloat : Follow { }\n"
|
||||
"}\n",
|
||||
)
|
||||
result = extract([f], cache_root=tmp_path)
|
||||
follow_class = next(
|
||||
n for n in result["nodes"]
|
||||
if n.get("label") == "Follow" and "effectkind" not in str(n.get("id", "")).lower()
|
||||
)
|
||||
follow_member = next(
|
||||
n for n in result["nodes"]
|
||||
if n.get("label") == "Follow" and "effectkind" in str(n.get("id", "")).lower()
|
||||
)
|
||||
followfloat = next(n for n in result["nodes"] if n.get("label") == "FollowFloat")
|
||||
inh = {(e["source"], e["target"]) for e in result["edges"] if e.get("relation") == "inherits"}
|
||||
|
||||
assert (followfloat["id"], follow_class["id"]) in inh, \
|
||||
"FollowFloat must inherit from class Follow"
|
||||
assert (followfloat["id"], follow_member["id"]) not in inh, \
|
||||
"FollowFloat must NOT inherit from enum member EffectKind.Follow"
|
||||
|
||||
|
||||
def test_csharp_enum_member_cannot_steal_external_parameter_type(tmp_path: Path):
|
||||
# Repro B (#3795): Vector2 parameter must not bind to Channel.Vector2 enum member.
|
||||
kinds = _write(
|
||||
tmp_path / "Kinds.cs",
|
||||
"namespace Demo {\n"
|
||||
" public enum Channel { Vector2 }\n"
|
||||
"}\n",
|
||||
)
|
||||
mover = _write(
|
||||
tmp_path / "Mover.cs",
|
||||
"using UnityEngine;\n"
|
||||
"namespace Demo {\n"
|
||||
" public class Mover {\n"
|
||||
" public void Move(Vector2 delta) { }\n"
|
||||
" }\n"
|
||||
"}\n",
|
||||
)
|
||||
result = extract([kinds, mover], cache_root=tmp_path)
|
||||
channel_v2 = next(
|
||||
n for n in result["nodes"]
|
||||
if n.get("label") == "Vector2" and n.get("source_file")
|
||||
)
|
||||
move_refs = [
|
||||
e["target"] for e in result["edges"]
|
||||
if e.get("relation") == "references"
|
||||
and e.get("context") == "parameter_type"
|
||||
and "move" in str(e.get("source", "")).lower()
|
||||
]
|
||||
assert channel_v2["id"] not in move_refs, \
|
||||
"Mover.Move(Vector2) must NOT resolve to Channel.Vector2 enum member"
|
||||
by_id = {n["id"]: n for n in result["nodes"]}
|
||||
assert any(
|
||||
by_id[tgt].get("label") == "Vector2" and not by_id[tgt].get("source_file")
|
||||
for tgt in move_refs
|
||||
), f"Vector2 parameter must remain on an external stub: {move_refs}"
|
||||
|
||||
|
||||
def test_csharp_property_cannot_steal_external_parameter_type(tmp_path: Path):
|
||||
# Repro B (#3795): System.Type parameter must not bind to Node.Type property.
|
||||
node = _write(
|
||||
tmp_path / "Node.cs",
|
||||
"namespace Demo {\n"
|
||||
" public class Node {\n"
|
||||
" public System.Type Type { get; set; }\n"
|
||||
" }\n"
|
||||
"}\n",
|
||||
)
|
||||
mover = _write(
|
||||
tmp_path / "Mover.cs",
|
||||
"using System;\n"
|
||||
"namespace Demo {\n"
|
||||
" public class Mover {\n"
|
||||
" public string Describe(Type t) => t.Name;\n"
|
||||
" }\n"
|
||||
"}\n",
|
||||
)
|
||||
result = extract([node, mover], cache_root=tmp_path)
|
||||
node_prop_type = next(
|
||||
n for n in result["nodes"]
|
||||
if n.get("label") == "Type" and n.get("source_file")
|
||||
)
|
||||
describe_refs = [
|
||||
e["target"] for e in result["edges"]
|
||||
if e.get("relation") == "references"
|
||||
and e.get("context") == "parameter_type"
|
||||
and "describe" in str(e.get("source", "")).lower()
|
||||
]
|
||||
assert node_prop_type["id"] not in describe_refs, \
|
||||
"Mover.Describe(Type) must NOT resolve to Node.Type property"
|
||||
by_id = {n["id"]: n for n in result["nodes"]}
|
||||
assert any(
|
||||
by_id[tgt].get("label") == "Type" and not by_id[tgt].get("source_file")
|
||||
for tgt in describe_refs
|
||||
), f"Type parameter must remain on an external stub: {describe_refs}"
|
||||
|
||||
|
||||
def test_csharp_cross_file_collision_resolved_by_kind_not_filename_order(tmp_path: Path):
|
||||
# Enum member in A_Enum.cs (alphabetically first) must not steal inheritance from class in Follow.cs.
|
||||
a_enum = _write(
|
||||
tmp_path / "A_Enum.cs",
|
||||
"namespace Demo {\n"
|
||||
" public enum EffectKind { Tween, Follow }\n"
|
||||
"}\n",
|
||||
)
|
||||
follow = _write(
|
||||
tmp_path / "Follow.cs",
|
||||
"namespace Demo {\n"
|
||||
" public abstract class Follow { }\n"
|
||||
" public sealed class FollowFloat : Follow { }\n"
|
||||
"}\n",
|
||||
)
|
||||
result = extract([a_enum, follow], cache_root=tmp_path)
|
||||
follow_class = next(
|
||||
n for n in result["nodes"]
|
||||
if n.get("label") == "Follow" and "follow.cs" in str(n.get("source_file", "")).lower()
|
||||
)
|
||||
follow_member = next(
|
||||
n for n in result["nodes"]
|
||||
if n.get("label") == "Follow" and "a_enum.cs" in str(n.get("source_file", "")).lower()
|
||||
)
|
||||
followfloat = next(n for n in result["nodes"] if n.get("label") == "FollowFloat")
|
||||
inh = {(e["source"], e["target"]) for e in result["edges"] if e.get("relation") == "inherits"}
|
||||
|
||||
assert (followfloat["id"], follow_class["id"]) in inh, \
|
||||
"FollowFloat must inherit from class Follow despite A_Enum.cs sorting first"
|
||||
assert (followfloat["id"], follow_member["id"]) not in inh, \
|
||||
"FollowFloat must NOT inherit from enum member EffectKind.Follow in A_Enum.cs"
|
||||
Reference in new issue
Block a user