mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-12 23:20:03 +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>
(cherry picked from commit e6ccd59f20)
This commit is contained in:
1 parent
1adadc7f79
commit
bcf9624477
2 files changed
+63
No files matched your search
@@ -5640,6 +5640,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