Skip to content

Explicit OVERRIDE detection misses methods inherited through intermediate classes #1278

Description

@Enferlain

Version

codebase-memory-mcp dev

Platform

Linux (x64)

What happened?

Explicit OVERRIDE detection misses a method when the declaring contract is inherited through an intermediate class that does not redeclare the method.

This leaves a valid runtime implementation disconnected from the contract method even though the class inheritance chain and the canonical base-typed call are both present.

Minimal reproduction

from abc import ABC, abstractmethod

class Processor(ABC):
    @abstractmethod
    def process(self) -> str:
        raise NotImplementedError

class IntermediateProcessor(Processor):
    pass

class LeafProcessor(IntermediateProcessor):
    def process(self) -> str:
        return "leaf"

def run(processor: Processor) -> str:
    return processor.process()

Run a full index and inspect the relevant edges.

The graph correctly contains:

IntermediateProcessor -INHERITS-> Processor
LeafProcessor         -INHERITS-> IntermediateProcessor

run -CALLS-> Processor.process
confidence=0.90
strategy=lsp_method

But neither of these queries returns an OVERRIDE edge:

MATCH (impl:Method)-[r:OVERRIDE]->(base:Method)
WHERE impl.qualified_name CONTAINS 'LeafProcessor.process'
RETURN impl.qualified_name, base.qualified_name
MATCH (impl:Method)-[r:OVERRIDE]->(base:Method)
WHERE base.qualified_name CONTAINS 'Processor.process'
RETURN impl.qualified_name, base.qualified_name

A direct-inheritance control works:

class DirectProcessor(Processor):
    def process(self) -> str:
        return "direct"

That produces:

DirectProcessor.process -OVERRIDE-> Processor.process

All fixture files reported no_recorded_issue from check_index_coverage.

Likely cause

On current main, cbm_pipeline_override_explicit() iterates direct IMPLEMENTS / INHERITS edges and override_match_methods() compares the child class's methods only with methods declared on that immediate base.

For LeafProcessor, the immediate base is IntermediateProcessor, which declares no process method. The search stops there and never reaches Processor.process.

Expected behavior

LeafProcessor.process should be connected to the inherited declaration it overrides:

LeafProcessor.process -OVERRIDE-> Processor.process

The override builder should walk the ancestor chain until it finds the nearest matching declaration, with cycle protection and deduplication. If the graph intentionally stores all overridden declarations rather than only the nearest declaration, that policy should be explicit and consistently tested.

Impact

Any polymorphic call expansion based on:

caller -CALLS-> contract <-OVERRIDE- implementations

will omit leaf implementations behind non-overriding intermediate classes. Inbound caller tracing, impact analysis, and override-set queries therefore under-report valid implementations.

This is independent of receiver inference: the CALLS edge to the contract is already correct.

Regression test

A graph-level test should assert all of the following:

  • direct override: DirectProcessor.process -> Processor.process
  • indirect override through an empty intermediate: LeafProcessor.process -> Processor.process
  • no duplicate OVERRIDE edges
  • inheritance cycles or malformed hierarchies terminate safely

Related

Confirmations

  • I searched existing issues and found no duplicate.
  • The reproduction is a standalone synthetic fixture.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcypherCypher query language parser/executor bugsparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions