fix(extract): link a PHP construction site to the class it constructs - #3178
fix(extract): link a PHP construction site to the class it constructs#3178rajanpanth wants to merge 1 commit into
Conversation
…Graphify-Labs#3115) _PHP_CONFIG.call_types never listed object_creation_expression, so new Foo(...) never dispatched into walk_calls and a method that only constructed a class got no edge to it at all. Java has treated this as a call since Graphify-Labs#1373, C# caught up in Graphify-Labs#2998; PHP had not. Unlike Java and C#, whose object_creation_expression exposes the constructed type through a type field, tree-sitter-php exposes no named fields on this node at all: new, the class name, and the argument list are purely positional. Reused neither existing branch and instead scan the node's children directly for a name or qualified_name child, keeping only the last namespace segment to match the existing scoped_call_expression/Java/C# convention. 6 new tests cover assignment position, argument position (the message-bus shape from the issue, where the construction never appears in a declared position), namespace-qualified construction, and regression guards for the two call shapes already in call_types.
There was a problem hiding this comment.
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. No changes could be formally verified in this run.
Graphify review — findings
Treats PHP new Foo(...) as a call, so a method that only constructs a class now gets a calls edge to it — covering the message-bus shape $bus->dispatch(new SomeCommand(...)) where construction is the control flow. Since tree-sitter-php exposes no named fields on object_creation_expression, _extract_generic gets a dedicated PHP branch that scans positional children for the name/qualified_name node and takes its last namespace segment. Existing static (Baz::create()) and member ($obj->work()) call resolution is unaffected.
Worth a look
- PHP extraction now reports object construction as calls edges —
graphify/extract.py:1001· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- PHP constructor calls discard qualified namespaces before resolution —
graphify/extractors/engine.py:5303· 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 — 1923 functions depend on the 424 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 502 callers, 42 callees - new:
_rebuild_code()— 98 callers, 50 callees - new:
_extract_generic()— 18 callers, 24 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_js()— 85 callers, 3 callees - new:
dispatch_command()— 2 callers, 122 callees - new:
extract_objc()— 27 callers, 9 callees - new:
_get_extractor()— 26 callers, 6 callees - …and 32 more — each is listed as a finding
Verification — 1923 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: 1766 function(s) in the blast radius were not formally verified this run
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: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 40 more finding(s) on lines outside this diff (see the check run).
|
Thanks @rajanpanth — we went with #3169 for the PHP object-creation edge (#3115), which shipped in v0.9.52 with the same calls-edge modeling as the C#/TS handlers plus an explicit self/static/parent guard. Your PR was functionally equivalent; closing as superseded, with appreciation. Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.52 |
Fixes #3115.
What
_PHP_CONFIG.call_typesnever listedobject_creation_expression, sonew Foo(...)never dispatched intowalk_callsand a PHP method that only constructs a class — never calls a method on it — got no edge to it at all.Why
Same gap Java closed in #1373 and C# closed in #2998, just not caught up for PHP yet. The issue's own numbers make the practical cost concrete: on a 615-file Symfony/DDD codebase, 505
new X(sites across 178 classes had no edge, and on message-bus code specifically ($bus->dispatch(new SomeCommand(...))), 0 of 22 dispatch sites linked to the command they construct — exactly the shape where construction is the control flow.How
Added
object_creation_expressionto_PHP_CONFIG.call_typesinextract.py.That alone isn't sufficient, though — I checked how Java and C# handle the same node type in
walk_calls(extractors/engine.py) before assuming the config change was the whole fix, since both needed a dedicated per-language branch there (their grammars expose the constructed type through atypefield, which neither the generic nor either language's existing call-handling path reads by default). Confirmed empirically that tree-sitter-php'sobject_creation_expressionis different again: it exposes no named fields at all —new, the class name, and the argument list are purely positional children (checked directly against the parsed tree, not just the grammar docs). So this needed its own branch: scan the node's children for anameorqualified_nametype directly, keeping only the last namespace segment on a qualified name to match the existingscoped_call_expression/Java/C# convention for namespace-qualified names.Test plan
Reproduced the issue's exact repro (
Foo/Barconstructed positionally,Baz::create()as the working control) via the CLI directly: 11 edges before the fix (only the static call), 13 after (both construction sites now present), confirmed with the extraction cache cleared between runs so the "before" run wasn't reusing cached "after" output.Added
tests/test_php_object_creation.py, 6 cases: assignment position, argument position (the message-bus shape from the issue), namespace-qualified construction, and two regression guards confirmingscoped_call_expression/member_call_expressionstill resolve correctly with the new branch in place. Reverting only the two source files (keeping the tests) fails 4 of 6 — the 2 regression-guard tests correctly still pass, since that code path is untouched.Ran the full existing PHP-related suite (
test_languages.py,test_php_type_resolution.py,test_multilang.py,test_csharp_object_creation.pyas a sanity check on the analogous C# path) plus the new file: 47 passed, 4 skipped, no regressions. Ran the broadertests/suite too; the only failure (test_extract_code_only_cli.py::test_mixed_repo_without_key_errors_and_points_at_code_only) reproduces identically on an unmodified checkout — it's anopenaipackage availability difference in this environment, unrelated to this change.ruff checkclean on all three changed files.ruff format --checkreports pre-existing (unmodified-baseline) formatting differences on the two source files unrelated to my diff, so I left formatting as-is rather than reformat unrelated code.