Skip to content

affected on an interface method never reaches the implementation: no method-level implements edge (PE-80043) #70

Description

@filipechagas

Filed from LawnStarter adoption ticket PE-80043 (api.lawnstarter.com), graded run at CLI 0.9.40, fork v8 @ 5e9d95c11837aeabbbb5d90cb45544f49dc58ca4.

Summary

graphify affected on an interface method never reaches the implementing method, at any depth. The graph records implements at the class level only; there is no method-level interface → implementation edge. So the single most important answer to "what breaks if I change this interface method?" — the class that implements it — is silently absent.

This is the "silent incompleteness" failure mode: no edge, no warning. The blast radius looks small and correct.

Reproduction

Corpus: api.lawnstarter.com @ 382fe1a2c1 (private LawnStarter repo, PHP/Laravel, 6,224 files).

Minimal shape:

// app/Repositories/ScheduleEvent/ScheduleEventRepository.php:138
interface ScheduleEventRepository
{
    public function bulkUpdateDistributionId(Collection $scheduleEvents, int $distributionId): void;
}

// app/Repositories/ScheduleEvent/DbScheduleEventRepository.php:71
class DbScheduleEventRepository extends DbRepository implements ScheduleEventRepository
{
    // :2106
    public function bulkUpdateDistributionId(Collection $scheduleEvents, int $distributionId): void { /* ... */ }
}
graphify update .
graphify affected "app_repositories_scheduleevent_scheduleeventrepository_scheduleeventrepository_bulkupdatedistributionid" --depth 2

Expected

Everything that breaks if that signature changes:

  1. app/Repositories/ScheduleEvent/DbScheduleEventRepository.php:2106 — the implementing method
  2. app/Actions/Distributions/BaseDistributionCreator.php:73 — the only production caller
  3. app/Actions/Distributions/BaseDistributionCreator.php:47 — transitive, handle() calls bindEventsToDistribution()

Ground truth by grep, all 8 hits adjudicated. The 5 hits in BaseDistributionCreatorTest.php are Mockery shouldReceive('bulkUpdateDistributionId') string literals — correctly excluded as string mentions, and correctly absent from the output.

Actual

Affected nodes for .bulkUpdateDistributionId()
Relations: calls, indirect_call, references, imports, imports_from, re_exports, inherits, extends, implements, uses, mixes_in, embeds, requires
Depth: 2
- .bindEventsToDistribution() [calls] app/Actions/Distributions/BaseDistributionCreator.php:L73
- .handle() [calls] app/Actions/Distributions/BaseDistributionCreator.php:L47

2 of 3. The implementation is missing. --depth 3 returns byte-identical output, so raising depth does not help.

Note that implements is already in the traversed relation list — it just has nothing to traverse at method granularity.

Root cause

Inspecting graph.json directly:

=== iface method inbound ===
   calls  | .bindEventsToDistribution() | app/Actions/Distributions/BaseDistributionCreator.php L73
   method | ScheduleEventRepository     | app/Repositories/ScheduleEvent/ScheduleEventRepository.php L138

=== impl method inbound ===
   method | DbScheduleEventRepository   | app/Repositories/ScheduleEvent/DbScheduleEventRepository.php L2106

=== impl class -> iface class edges ===
   implements

The class-level implements edge exists. The two method nodes are connected to nothing but their own owning classes.

Suggestion

When class C implements I, emit an edge between C::m and I::m for each method name declared on I and defined on C — the match is purely by name within an already-established implements pair, so it needs no type inference. The same applies to abstract methods under inherits.

Failing that, having affected walk method -> owning class -> implements -> implementing class -> method of same name would recover it at traversal time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions