Skip to content

fix(extract): emit inherits edge for Python parameterized bases#1815

Open
Synvoya wants to merge 1 commit into
Graphify-Labs:v8from
Synvoya:fix/python-parameterized-base-inherits
Open

fix(extract): emit inherits edge for Python parameterized bases#1815
Synvoya wants to merge 1 commit into
Graphify-Labs:v8from
Synvoya:fix/python-parameterized-base-inherits

Conversation

@Synvoya

@Synvoya Synvoya commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Bug

A Python class whose base is parameterized loses its heritage edge.
class Repo(Base[int]) and class Handler(Generic[T]) emit no
inherits edge at all, so every generic base — Generic[T],
Sequence[T], Mapping[K, V], Repository[User], list[str] — is
dropped from the graph.

Root cause

The Python branch of _extract_generic (graphify/extractors/engine.py)
iterates the superclasses list and only handles bases whose node type is
identifier:

for arg in args.children:
    if arg.type == "identifier":
        ...
        add_edge(class_nid, base_nid, "inherits", line)

A parameterized base does not parse as an identifier. tree-sitter-python
wraps it in a subscript node whose value field is the real base type and
whose [...] holds only the type parameters:

class_definition "class Repo(Base[int])"
  superclasses: argument_list
    subscript "Base[int]"
      value: identifier "Base"   <-- the actual base
      subscript: integer "int"

The subscript node never matched the identifier check, so the base fell
through and the edge was silently dropped.

Minimal repro

class Base: pass
class Repo(Base[int]): pass       # inherits Repo -> Base  (dropped)
class Multi(Mixin, Base[str]): pass  # Mixin kept, Base dropped

extract_python before this change yields only the bare-identifier edges;
every subscript base is missing.

Fix

Unwrap a subscript base to its value field before the identifier check:

if base_node.type == "subscript":
    base_node = base_node.child_by_field_name("value") or base_node
if base_node.type == "identifier":
    ...

Test

tests/test_languages.py::test_python_parameterized_base_inherits_edge
asserts Repo -> Base and Multi -> Base (subscript bases) plus
Multi -> Mixin (bare-identifier regression guard).

  • Before: fails — inherits == {('Multi', 'Mixin')}; both subscript
    bases dropped.
  • After: passes.

Full tests/test_languages.py suite: 315 passed, 13 skipped (unchanged;
skips are the optional tree-sitter-dm extra).

The Python heritage handler in _extract_generic only matched bare
`identifier` bases in a class's `superclasses` list. A parameterized
base -- `class Repo(Base[int])`, `class C(Generic[T])` -- parses as a
`subscript` node whose `value` field is the actual base type and whose
`[...]` holds only type parameters. That node type fell through the
identifier-only check, so every generic base silently lost its
`inherits` edge.

Unwrap a `subscript` base to its `value` before the identifier check,
so parameterized bases point their heritage edge at the base type.
Bare-identifier bases are unaffected. Qualified (`attribute`) bases
remain handled by the cross-file symbol resolver and are out of scope
here.

Regression test in tests/test_languages.py fails before, passes after.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant