Skip to content

fix(php): preserve known-self calls alongside construct filtering - #4119

Closed
xiehuanyi wants to merge 1 commit into
Graphify-Labs:v8from
xiehuanyi:test/php-construct-binding-boundaries
Closed

xiehuanyi wants to merge 1 commit into
Graphify-Labs:v8from
xiehuanyi:test/php-construct-binding-boundaries

Conversation

@xiehuanyi

Copy link
Copy Markdown
Contributor

What does this PR do?

The construct-boundary coverage requested in #4075 exposes a missing real call: PHP $this->list() is deferred by the shared cross-language builtin-name guard, even when list is a method in the current class.

Resolve builtin-named methods on a known PHP $this receiver through the existing owning-class method table, with PHP case matching. Require an enclosing method owner; a free or dynamically bound closure must not bind another class's method. Free functions and unknown receivers retain their existing boundaries.

Closes #4075.

Related: #2617 handles broader PHP typed-receiver resolution. This change targets the plain $this builtin-name defer arm, which that diff retains. It does not add the broader typed-receiver feature.

Type of change

  • Bug fix
  • Tests or CI

Verification & Invariants

Language constructs never bind to same-named methods, while actual known-self method calls remain callable. Class identity and method case matching select the owning method. An unknown owner cannot fall back to a file-wide class match.

  • Read CONTRIBUTING.md and repository instructions.
  • Reproduced missing list and uppercase known-self calls on unmodified v8.
  • Kept the production fix to the shared engine's known-PHP-self arm.
  • Parametrized six constructs and added class/case/free-function/unknown-owner boundaries.
  • Description matches the final implementation.
  • Limitations: same-file owning-class resolution; no new static-call or arbitrary receiver resolver.

How was this tested?

  • Unmodified v8: two new positive regressions fail, fourteen controls pass.
  • PHP focused suite: 17 passed. Independent review also identified and protected the no-class-owner boundary.
  • Full suite: 6562 passed, 14 skipped.
  • Ruff and AST-only graphify update .: passed.
  • Pyright: exactly the same 598 existing errors as unmodified v8, with an identical error-message multiset and explicit Python environment. This is not a clean whole-project typecheck.
  • git diff --check: passed.

Graphify-specific checklist

  • No skill fragments changed; generated skill refresh is not applicable.
  • Structural extraction remains deterministic.
  • Reviewed scope and target identity; no unsafe shell interpolation added.
  • No API keys or local graph artifacts included.
  • OpenAI Codex (GPT-6) assistance disclosed in commit metadata.

Copilot AI balanced review requested due to automatic review settings October 5, 2026 16:24
@xiehuanyi
xiehuanyi requested a review from safishamsi as a code owner October 5, 2026 16:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

Thanks for the pull request, @xiehuanyi. A maintainer will review it soon.

Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions.

A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic.

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. PR-changed functions: 1/2 verified (0 proven, 1 may-equivalent, 0 distinguished) · 1 not verified (1 unsupported).

Not verified on this run: \_extract\_generic (unsupported).


Graphify review — findings

Resolves PHP $this->name() calls whose method name collides with a language construct or builtin (list, die, open, …) to the caller's own class method through _self_call_target, matching PHP's case-insensitive method names. The new require_method_owner flag drops the bare-label fallback, so these calls never bind to a free function or another class's method. Without an enclosing class the call stays unresolved, and keyword uses like list($x) = … still produce no edge.

Worth a look

  • PHP owner-required self calls can still fall back to an unrelated class method — graphify/extractors/engine.py:1438 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • PHP method table is casefolded but non-builtin $this lookups are not — graphify/extractors/engine.py:7367 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 812 functions depend on the 288 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _extract_generic() — 18 callers, 31 callees
  • new: extract_js() — 87 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: extract_julia() — 19 callers, 7 callees
  • new: extract_svelte() — 14 callers, 7 callees
  • new: extract_cpp() — 32 callers, 3 callees
  • new: extract_vue() — 10 callers, 7 callees
  • …and 10 more — each is listed as a finding

Verification — 812 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 721 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

30 of 329 test file(s) selected (9%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_build.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_php_closures.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_indirect_call_block_scoped_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_js_exported_scalar_bindings.py — impact
  • tests/test_languages.py — impact
  • tests/test_multilang.py — impact
  • tests/test_php_language_construct_calls.py — impact, changed-test
  • tests/test_python_underscore_resolution.py — impact
  • tests/test_rationale.py — impact
  • tests/test_ruby_resolution.py — impact
  • tests/test_scala_context_bounds.py — impact
  • tests/test_scala_self_type.py — impact
  • tests/test_scala_top_level_binding.py — impact
  • tests/test_scala_type_definition.py — impact
  • tests/test_svelte_extraction.py — impact
  • tests/test_swift_computed_properties.py — impact
  • tests/test_swift_protocol_requirements.py — impact
  • tests/test_trailing_newline_not_a_syntax_error.py — impact
  • tests/test_ts_new_expression_calls.py — impact
  • tests/test_typescript_module_extensions.py — impact
  • tests/test_vue_extraction.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: parameter `config` is annotated `LanguageConfig` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_self\_call\_target (not a proof).

The verifier ran both versions of \_self\_call\_target on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 18 more finding(s) on lines outside this diff (see the check run).

@safishamsi

Copy link
Copy Markdown
Member

Landed in v0.9.77 via an authorship-preserving cherry-pick, so your commit is on v8 with you credited as the author. Closing as shipped — thanks @xiehuanyi!

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.

test(php): parametrize the language-construct test across all constructs

3 participants