mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 16:09:26 +00:00
feat(scala): link classes to typeclasses in derives clauses (#2048)
`case class Point(x: Int) derives Show, Eq` (and the same on enums) now emits a `references` edge with the `derives` context from the type to each derived typeclass, next to the class-level context bounds it resembles. A qualified name (`derives cats.Show`) links by its last segment. Co-Authored-By: Grok Bot <noreply@x.ai>
This commit is contained in:
1 parent
5c7b84792f
commit
e6ccd59f20
2 files changed
+63
No files matched your search
@@ -5159,6 +5159,20 @@ def _extract_generic(
|
||||
if target_nid != class_nid:
|
||||
add_edge(class_nid, target_nid, "references",
|
||||
line, context=ctx)
|
||||
# `derives Show, cats.Eq` (on classes and enums) names typeclasses
|
||||
# the type depends on, like a context bound (#2048). A qualified
|
||||
# name links by its last segment.
|
||||
derive = node.child_by_field_name("derive")
|
||||
for d in derive.children_by_field_name("type") if derive is not None else ():
|
||||
tail = d if d.type == "type_identifier" else next(
|
||||
(x for x in reversed(d.children) if x.type == "type_identifier"), None)
|
||||
if tail is None:
|
||||
continue
|
||||
d_line = d.start_point[0] + 1
|
||||
target_nid = ensure_named_node(_read_text(tail, source), d_line)
|
||||
if target_nid != class_nid:
|
||||
add_edge(class_nid, target_nid, "references",
|
||||
d_line, context="derives")
|
||||
|
||||
# C#: a primary constructor (`class Foo(IBar bar)`, C# 12+) declares
|
||||
# its dependencies on the type declaration itself rather than in a
|
||||
|
||||
@@ -228,3 +228,52 @@ def test_class_bound_edge_is_tagged_extracted_with_a_location(tmp_path):
|
||||
for e in edges:
|
||||
assert e.get("confidence") == "EXTRACTED", e
|
||||
assert e.get("source_location") == "L7", e
|
||||
|
||||
|
||||
# `derives` clauses (#2048) name typeclasses the type depends on, the same kind
|
||||
# of dependency as a context bound, so each one is a `references` edge with the
|
||||
# `derives` context. Grammar: a `derive` field on class_definition and
|
||||
# enum_definition, one `type` child per derived typeclass.
|
||||
DERIVES_SRC = '''\
|
||||
trait Show
|
||||
trait Eq
|
||||
|
||||
case class Point(x: Int) derives Show, Eq
|
||||
|
||||
enum Color derives Eq { case Red }
|
||||
|
||||
case class Wrapped(x: Int) derives cats.Show
|
||||
|
||||
class Bare(x: Int)
|
||||
'''
|
||||
|
||||
|
||||
def _derives_targets(tmp_path, label):
|
||||
(tmp_path / "Derives.scala").write_text(DERIVES_SRC)
|
||||
r = extract_scala(tmp_path / "Derives.scala")
|
||||
nid = {n["label"]: n["id"] for n in r["nodes"]}
|
||||
targets = {e["target"] for e in r["edges"]
|
||||
if e["relation"] == "references"
|
||||
and e["source"] == nid[label]
|
||||
and e.get("context") == "derives"}
|
||||
return targets, nid
|
||||
|
||||
|
||||
def test_case_class_derives_each_typeclass(tmp_path):
|
||||
targets, nid = _derives_targets(tmp_path, "Point")
|
||||
assert targets == {nid["Show"], nid["Eq"]}
|
||||
|
||||
|
||||
def test_enum_derives_typeclass(tmp_path):
|
||||
targets, nid = _derives_targets(tmp_path, "Color")
|
||||
assert targets == {nid["Eq"]}
|
||||
|
||||
|
||||
def test_qualified_derives_links_by_last_segment(tmp_path):
|
||||
targets, nid = _derives_targets(tmp_path, "Wrapped")
|
||||
assert targets == {nid["Show"]}
|
||||
|
||||
|
||||
def test_class_without_derives_emits_no_derives_edge(tmp_path):
|
||||
targets, _ = _derives_targets(tmp_path, "Bare")
|
||||
assert not targets
|
||||
Reference in new issue
Block a user