Skip to content

Build alias index binds a live Dart variable_type reference to a Java file node across languages (0.9.100) #4941

Closed

Description

@EthgarHernan

Summary

extract() correctly leaves an unresolved Dart type reference as a dangling stub (punchtype), but build_from_json() then resolves that endpoint through _alias_index() to an unrelated Java file node, because the alias is committed when exactly one node claims the old-stem spelling — with no language check. On a real repo this produces the only cross-language code edge, and it is an articulation edge that dominates betweenness.

Environment

  • graphifyy 0.9.100 (pip), Python 3.10, Windows 11
  • fresh AST cache (0/3 files served), no LLM involved

Minimal reproduction

Three files:

// mobile/lib/domain/punch.dart
enum PunchType { checkIn, checkOut }          // L3

class PunchDraft {
  final String employeeId;
  final PunchType type;                       // L27 - the reference
}
// backend/.../PunchType.java
public enum PunchType { CHECK_IN, CHECK_OUT }
// backend/.../AttendancePunch.java  (present so the enum has a same-corpus user)
public record AttendancePunch(UUID deviceId, PunchType type) {}
from pathlib import Path
from graphify.extract import extract
from graphify.build import build_from_json

files = [
    Path('mobile/lib/domain/punch.dart'),
    Path('backend/.../PunchType.java'),
    Path('backend/.../AttendancePunch.java'),
]
r = extract(files, cache_root=<fresh dir>, root=Path('.'))
# raw edge: mobile_lib_domain_punch --references--> punchtype   (dangling, correct)
G = build_from_json(r, root='.', directed=False)

Actual (built)

mobile_lib_domain_punch --references [EXTRACTED, context=variable_type, L27]-->
  backend_src_main_java_ec_smartlocalizador_attendance_punchtype

where the target node is labeled PunchType.java with source_file=backend/.../PunchType.java.

Expected

mobile_lib_domain_punch --references--> mobile_lib_domain_punch_punchtype

(label PunchType, defined in the same Dart file at L3), or a dangling stub if same-language cross-file resolution is unavailable. It should never bind to PunchType.java.

Where it happens

  • extract() emits target punchtype (dangling stub) — correct.
  • build.py::_alias_index (around L1142–1218, applied at L1793) registers _old_file_stems(rel) for every node with a source_file. For backend/.../PunchType.java those forms are attendance_punchtype and the bare punchtype. The Dart endpoint punchtype matches the bare alias; the alias has exactly one claimant, so the norm_to_id.setdefault(...) commit binds it to the Java file node.
  • The uniqueness rule protects against ambiguity between corpus files, but not against a same-named node in a different language. The comment at L1767–1777 already flags "silent cross-file (and cross-language) merge" as the hazard that uniqueness is meant to prevent — it does not prevent this case, because the correctly-named same-language node is not a claimant of the alias.

Relationship to existing issues

Impact (real repo: 1006 nodes / 1513 edges)

  • This is the only dart -> java edge among all 1513 edges (scanned across dart/java/ts/kotlin).
  • It is an articulation edge: removing the PunchType node splits the main connected component 771 -> 232 + 536, where the 232-node side is the entire Flutter app lobe.
  • Betweenness inflated: PunchType 0.2495, punch.dart 0.2532, AttendancePunch 0.2493, PunchType.java 0.2468 — all on this one chain. graphify's own GRAPH_REPORT surfaced it as a top God Node and "surprising connection".
  • Mobile and backend couple over HTTP, never by import; the graph should not connect them here.

Suggested fix

Gate alias resolution on language/extension compatibility at the edge-resolution site (where the edge's source_file is known), reusing the _lang_family helper already used in _rewire_unique_stub_nodes: when looking up an endpoint alias, skip candidates whose source_file language family differs from the edge source's family. Uniqueness is the wrong safety property when the correct answer is "none of the above" — the same conclusion as #1749.

An alternative narrow rule: only honor cross-language aliases for endpoints that are demonstrably stale pre-migration ids (they match a node's _old_file_stems form in the edge's own language), never for bare type names emitted as live references.

I can test a patch against the repro above if useful.

Activity

  1. github-actions commented on Nov 1, 2026

    @github-actions

    Thanks for opening this issue, @EthgarHernan. A maintainer will take a look soon.

    If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.

    To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.

  2. safishamsi commented on Nov 2, 2026

    @safishamsi
    Member

    Fixed in 0.9.101 (published to PyPI) via #4949. The file-stem alias index only rebinds an edge when the edge's file and the alias target share a language family, so a Dart reference no longer binds onto an unrelated same-stemmed Java file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions