Skip to content

Prefer the alias for a PHP use import edge's target - #3432

Open
ayushcodes10 wants to merge 1 commit into
Graphify-Labs:v8from
ayushcodes10:fix-3421-php-use-alias-import
Open

Prefer the alias for a PHP use import edge's target#3432
ayushcodes10 wants to merge 1 commit into
Graphify-Labs:v8from
ayushcodes10:fix-3421-php-use-alias-import

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Fixes #3421.

_import_php always targeted the bare imported class name (the last segment of the qualified name), completely ignoring a use ... as ... alias. Two files importing the same external class under different local aliases — the exact real-world case an alias exists for, disambiguating two same-named classes from different namespaces — produced two different stub targets for one class, splitting its identity in the graph.

Root cause traced through two interacting pieces:

  • _import_php (in extract.py) always used the bare imported name (e.g. Client) as the provisional stub target, regardless of any alias.
  • _resolve_php_type_references (in resolution.py) re-points that stub to the correct external FQN stub by looking up the stub's own bare label in a per-file alias -> FQN map, which is keyed by the alias when the import has one. For a plain (non-aliased) import the bare label happens to match that key, so it resolves correctly. For an aliased import the bare label ("Client") never matches the key ("httpclient"), so the lookup misses and the edge stays stuck on the unresolved bare-name stub.

Confirmed against the exact repro from the issue:

NODE client | Client |
NODE guzzlehttp_client | GuzzleHttp\Client |
EDGE src_a -> guzzlehttp_client imports
EDGE src_b -> client imports        # wrong: same class, different target

After the fix, both files' imports edges (and their constructor-parameter references edges) consistently point at the single guzzlehttp_client node.

Also verified:

  • A grouped use with a mixed aliased/non-aliased clause (use GuzzleHttp\{Client, Exception as GuzzleException};) resolves both parts correctly.
  • Two different classes aliased specifically to avoid a bare-name collision (App\Models\Session as ModelSession vs Shopify\Auth\Session as ShopifySession) still resolve to two distinct nodes — the fix does not over-collapse.
  • use function/use const clauses are unaffected (pre-existing behavior, out of scope for this issue).

Test plan

  • Added test_php_aliased_import_edge_shares_target_with_plain_import and test_php_distinct_aliases_for_same_named_classes_do_not_collapse to tests/test_php_type_resolution.py
  • pytest tests/test_php_type_resolution.py and full pytest tests/ -k php pass
  • Full suite passes except pre-existing, unrelated failures (test_ollama_retry_cap.py, missing openai module in this environment; test_ts_normalizer_scales_linearly_on_large_files, a flaky wall-clock timing assertion, confirmed to pass in isolation)
  • python -m tools.skillgen --check passes

🤖 Generated with Claude Code

https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh

An aliased use clause (use GuzzleHttp\Client as HttpClient) always
targeted the bare imported name, ignoring the alias entirely. Two
files importing the same external class under different local
aliases, a real pattern for disambiguating two same named classes
from different namespaces, produced two different stub targets for
one class, splitting its identity. The cross file resolver repoints
a stub through the file's own alias to FQN map, keyed by the alias
when the import has one, so a bare name derived stub could never be
found under that key and stayed stuck, unresolved. The alias, when
present, is also what the rest of the file actually references, so
preferring it here keeps this edge's target consistent with those
reference edges too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh

@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.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

No blocking issues surfaced.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1849 functions depend on the 251 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 561 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 28 more — each is listed as a finding

Verification — 1849 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: 1684 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

102 of 259 test file(s) selected (39%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_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_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_java_member_calls.py — impact
  • tests/test_java_type_resolution.py — impact
  • tests/test_js_callback_calls.py — impact
  • tests/test_js_dynamic_import_affected.py — impact
  • … and 52 more

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 \_import\_php.

The verifier did not have enough to check \_import\_php, 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: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

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

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.

PHP: use A\B\C as D gives the import edge a target derived from the last segment, splitting the class identity

1 participant