mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 23:49:14 +00:00
fix(extract): drop Swift generic type parameters from references
A `struct Stack<Element>` / `func map<R>(...)` binds `Element`/`R` as local
names, not corpus symbols, but `_swift_collect_type_refs` appended the bare
identifiers used in a signature or stored-property type as type references and
ran them through the sourceless-stub path. Every generic type/function in the
corpus then collapsed its `T` onto one bare `t` node — a god node that pollutes
god-node rankings, since single-letter type parameters are not in the
analyzer's builtin-noise filter. C#, Java, TypeScript, Kotlin, Scala, C++, and
Go already exclude theirs.
Mirrors that pattern (and complements the `Self` keyword fix already in this
collector): a new `_swift_type_parameters_in_scope` helper walks up to every
enclosing declaration's `type_parameters` list and takes each `type_parameter`'s
first `type_identifier` as the bound name (a constraint like `T: Equatable` is a
separate `user_type`, left to reference), and `_swift_collect_type_refs`
auto-computes and threads that skip set. Genuine user types and the `Int`/
`String` builtin hubs are unaffected.
(cherry picked from commit 2e8a5c0f97)
This commit is contained in:
1 parent
5299fb1e81
commit
786e8868a0
2 files changed
+91
-8
No files matched your search
@@ -1193,28 +1193,63 @@ def _swift_user_type_name(user_type_node, source: bytes) -> str | None:
|
||||
return text or None
|
||||
return None
|
||||
|
||||
def _swift_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
|
||||
"""Walk a Swift type expression; append (name, role) tuples (role 'type' or 'generic_arg')."""
|
||||
def _swift_type_parameters_in_scope(node, source: bytes) -> frozenset[str]:
|
||||
"""Return Swift generic type-parameter names visible from ``node``.
|
||||
|
||||
A `struct Stack<Element>` / `func map<R>(...)` binds `Element`/`R` as local
|
||||
names, not corpus symbols. Walking up to every enclosing declaration's
|
||||
`type_parameters` list collects them; a parameter's name is the first
|
||||
`type_identifier` of its `type_parameter` node (a constraint like
|
||||
`T: Equatable` is a separate `user_type` and is left to reference).
|
||||
"""
|
||||
names: set[str] = set()
|
||||
scope = node
|
||||
while scope is not None:
|
||||
for child in scope.children:
|
||||
if child.type != "type_parameters":
|
||||
continue
|
||||
for param in child.children:
|
||||
if param.type != "type_parameter":
|
||||
continue
|
||||
for sub in param.children:
|
||||
if sub.type == "type_identifier":
|
||||
name = _read_text(sub, source)
|
||||
if name:
|
||||
names.add(name)
|
||||
break
|
||||
scope = scope.parent
|
||||
return frozenset(names)
|
||||
|
||||
|
||||
def _swift_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]],
|
||||
skip: frozenset[str] | None = None) -> None:
|
||||
"""Walk a Swift type expression; append (name, role) tuples (role 'type' or 'generic_arg').
|
||||
|
||||
Generic type parameters in scope (``skip``) are local bindings, not
|
||||
references; emitting one mints a sourceless stub (the bare `t` god node a
|
||||
`<T>` type or function would otherwise share corpus-wide)."""
|
||||
if node is None:
|
||||
return
|
||||
if skip is None:
|
||||
skip = _swift_type_parameters_in_scope(node, source)
|
||||
t = node.type
|
||||
if t == "type_annotation":
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_swift_collect_type_refs(c, source, generic, out)
|
||||
_swift_collect_type_refs(c, source, generic, out, skip)
|
||||
return
|
||||
if t == "user_type":
|
||||
for c in node.children:
|
||||
if c.type == "type_identifier":
|
||||
text = _read_text(c, source)
|
||||
if text and text != "Self":
|
||||
if text and text != "Self" and text not in skip:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
break
|
||||
for c in node.children:
|
||||
if c.type == "type_arguments":
|
||||
for arg in c.children:
|
||||
if arg.is_named:
|
||||
_swift_collect_type_refs(arg, source, True, out)
|
||||
_swift_collect_type_refs(arg, source, True, out, skip)
|
||||
return
|
||||
if t == "type_identifier":
|
||||
text = _read_text(node, source)
|
||||
@@ -1223,19 +1258,19 @@ def _swift_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple
|
||||
# method collapses onto (a god node), the same keyword-type pitfall as
|
||||
# Rust's `Self`. A genuine type (including `Int`/`String` builtin hubs)
|
||||
# is unaffected.
|
||||
if text and text != "Self":
|
||||
if text and text != "Self" and text not in skip:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
return
|
||||
if t in ("optional_type", "implicitly_unwrapped_optional_type", "array_type",
|
||||
"dictionary_type", "tuple_type"):
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_swift_collect_type_refs(c, source, generic, out)
|
||||
_swift_collect_type_refs(c, source, generic, out, skip)
|
||||
return
|
||||
if node.is_named:
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_swift_collect_type_refs(c, source, generic, out)
|
||||
_swift_collect_type_refs(c, source, generic, out, skip)
|
||||
|
||||
def _swift_property_type_node(property_node):
|
||||
"""Return the type_annotation child of a Swift property_declaration, if any."""
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Swift generic type parameters must not mint sourceless god nodes.
|
||||
|
||||
A `struct Stack<Element>` / `func map<R>(...)` binds `Element`/`R` as local
|
||||
names, not corpus symbols. The Swift type-ref pass appended the bare
|
||||
identifiers used in a signature or stored-property type as type references and
|
||||
ran them through the sourceless-stub path, so every generic type/function in
|
||||
the corpus collapsed its `T` onto one bare `t` node — a god node (single-letter
|
||||
type params are not in the analyzer's builtin-noise filter). C#, Java,
|
||||
TypeScript, Kotlin, Scala, C++, and Go already exclude their type parameters;
|
||||
Swift now matches.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.extract import extract
|
||||
|
||||
|
||||
def _sourceless(r):
|
||||
return sorted({(n["id"], n.get("label")) for n in r["nodes"] if not n.get("source_file")})
|
||||
|
||||
|
||||
def test_generic_struct_and_method_type_params_mint_no_stub(tmp_path):
|
||||
(tmp_path / "s.swift").write_text(
|
||||
"struct Stack<Element> {\n"
|
||||
" var items: [Element]\n"
|
||||
" func peek() -> Element? { return items.last }\n"
|
||||
" func map<R>(_ f: (Element) -> R) -> [R] { return [] }\n"
|
||||
"}\n"
|
||||
)
|
||||
r = extract([tmp_path / "s.swift"], cache_root=tmp_path, parallel=False)
|
||||
assert _sourceless(r) == []
|
||||
assert not any(e.get("target") in ("element", "r") for e in r["edges"])
|
||||
|
||||
|
||||
def test_constrained_param_dropped_but_real_type_kept(tmp_path):
|
||||
# `T` is a parameter (dropped); a genuine user type in the signature still
|
||||
# references.
|
||||
(tmp_path / "s.swift").write_text(
|
||||
"struct User { var id: Int }\n"
|
||||
"func wrap<T: Equatable>(_ x: T, u: User) -> User { return u }\n"
|
||||
)
|
||||
r = extract([tmp_path / "s.swift"], cache_root=tmp_path, parallel=False)
|
||||
assert not any(e.get("target") == "t" for e in r["edges"])
|
||||
refs = {
|
||||
rn["label"]
|
||||
for e in r["edges"] if e.get("relation") == "references"
|
||||
for rn in r["nodes"] if rn["id"] == e["target"]
|
||||
}
|
||||
assert "User" in refs
|
||||
Reference in new issue
Block a user