Skip to content

fix(parsers/php): resolve $this->/self:: calls into used traits#131

Open
gadievron wants to merge 1 commit into
pr/zig-php-generic-anonfrom
fix/php-trait-self-call-on-127
Open

fix(parsers/php): resolve $this->/self:: calls into used traits#131
gadievron wants to merge 1 commit into
pr/zig-php-generic-anonfrom
fix/php-trait-self-call-on-127

Conversation

@gadievron

Copy link
Copy Markdown
Collaborator

fix(parsers/php): resolve $this->/self:: calls into used traits

Base: pr/zig-php-generic-anon · Depends-on: #127 · Type: bug fix · Finding: F12 (HIGH)

Stacked on our own in-flight PR #127 (pr/zig-php-generic-anon), which already
lands the prerequisite PHP-parser fixes (untagged-fragment <?php prepend in
_extract_calls_from_code; relative_scope handling for self::/static::/
parent:: in _resolve_scoped_call). This PR adds ONLY the net-new
trait-composition delta — no overlap with #127's hunks.

What

  • parsers/php/function_extractor.py — capture in-class use_declaration trait
    names onto a new traits field on the class record (_extract_trait_names).
  • parsers/php/call_graph_builder.py — build a traits_by_class index and make
    _resolve_self_call fall back to the used traits' methods when no own method
    matches.

Why

$this->method() / self::method() calls into a method defined in a used trait
were never resolved: _resolve_self_call only indexed methods physically in the
class body, and trait use was never recorded as a class→trait composition. Trait
methods live under the trait's own key, so the edges were dropped. Measured: of
trait methods, symfony 77% orphaned (425/553), laravel 68%, composer 71%, guzzle
86% — a major contributor to PHP's high isolated-function ratio.

Tests

tests/test_php_trait_self_call.py (new): trait T{ g() }, class
C{ use T; f(){ $this->g(); } h(){ self::g(); } }; asserts edges C::f→T::g and
C::h→T::g (builder-dict + end-to-end extractor layers).

Scope / siblings

Ruby has the same shape (include/extend/prepend mixins never recorded as a
class→module composition; _resolve_self_call has no mixin fallback) — confirmed
sibling, tracked separately, not fixed here.

Upstream coordination

No open PR adds trait-composition resolution. PR #122 (php/ruby) handles only
conditional-branch same-name collisions, not traits — non-overlapping. Must merge
after #127.

Author notes

A class that composes a method via an in-class `use TraitName;` had no
call edge from `$this->m()` / `self::m()` to the trait's method:
`_resolve_self_call` only considered methods physically declared in the
class body, never the methods pulled in from used traits.

- function_extractor.py: capture in-class `use_declaration` trait names
  onto a new `traits` field on the class record (grouped + namespaced
  uses reduced to the unqualified trait name).
- call_graph_builder.py: build a `traits_by_class` index and make
  `_resolve_self_call` fall back to the used traits' methods (cross-file,
  via `_resolve_class_call`) when no own method matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gadievron

Copy link
Copy Markdown
Collaborator Author

Finding F12 (HIGH). ⚠️ Stacked on #127 (base pr/zig-php-generic-anon). #127 already lands the prerequisite PHP fixes (<?php tagless-parse, relative_scope); this PR adds ONLY the net-new class→trait composition index + _resolve_self_call trait fallback (symfony had 77% of trait methods orphaned). No overlap with #127's hunks. Merge after #127.

@gadievron gadievron marked this pull request as ready for review June 18, 2026 02:03
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