Skip to content

# fix(ruby): preserve suffixed method node IDs (#3077) - #3079

Closed
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:fix/3077-ruby-suffixed-method-ids
Closed

# fix(ruby): preserve suffixed method node IDs (#3077)#3079
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:fix/3077-ruby-suffixed-method-ids

Conversation

@hopstreax

Copy link
Copy Markdown
Contributor

Summary

Fixes #3077.

Ruby methods such as foo, foo!, foo?, and foo= were previously normalized to the same node ID. When defined in the same class/file, the first method encountered by the extractor was retained and the others were silently dropped by seen_ids.

This caused missing nodes and incorrect/missing call relationships for common Ruby bang, predicate, and setter methods.

What changed

  • Added an optional sanitize_symbol_name_fn to LanguageConfig.

  • Added Ruby-specific sanitization for trailing method suffixes:

    • !_bang
    • ?_pred
    • =_eq
  • Kept the global normalize_id() / make_id() behavior unchanged.

  • Preserved the original Ruby method spelling in node labels.

  • Updated Ruby member-call resolution to use the raw method spelling instead of stripping punctuation.

  • Added regression coverage for:

    • foo, foo!, foo?, and foo=
    • singleton methods
    • top-level methods
    • Ruby call resolution
    • suffixed-method ID stability across extraction updates

Example

Before:

def save
end

def save!
end

Both methods normalized to the same node ID, causing save! to be dropped.

After:

save  → *_save
save! → *_save_bang

Both methods are represented as distinct nodes while their labels retain the original Ruby spelling.

Validation

Focused Ruby/resolution tests:

37 passed

ID normalization and extraction-spec contract tests:

122 passed

Broader suite:

668 passed
4 skipped
2 failed

The two failures are unrelated Windows environment issues involving Unicode cp1252 encoding and deeply nested temporary paths.

Global ID normalization and other language extractors remain unchanged.

Scope

This PR addresses Ruby suffixed method names (!, ?, =). Ruby operator-method handling is intentionally left out of this change.

@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. No changes could be formally verified in this run.


Graphify review — findings

Encodes trailing Ruby method suffixes (!, ?, =) into node ID components (_bang/_pred/_eq) via a new _ruby_sanitize_method_name, so foo, foo!, foo?, and foo= get distinct, stable IDs instead of colliding, while keeping their raw .foo!() display labels. Wires this through _extract_generic with an optional sanitize_symbol_name_fn on LanguageConfig (defaults to identity for other languages) and applies it before the empty-name collision check. Fixes Ruby call resolution to index and look up methods by their raw suffixed name rather than the _key-normalized form, so a.save and a.save! resolve to separate targets.

Worth a look

  • Ruby suffix sanitizer collides with legitimate method namesgraphify/extract.py:901 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • LanguageConfig positional constructor contract shiftedgraphify/extractors/models.py:50 · 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 — 2210 functions depend on the 1021 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 491 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: dispatch_command() — 2 callers, 122 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: extract_js() — 80 callers, 3 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 34 more — each is listed as a finding

Verification — 2210 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: 2059 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

Could not verify: Could not verify resolve\_ruby\_member\_calls.

The verifier did not have enough to check resolve\_ruby\_member\_calls, 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: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

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

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.50 via authorship-preserving cherry-pick so you keep contributor-graph credit. Thanks @hopstreax! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.50

@safishamsi safishamsi closed this Aug 25, 2026
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.

Ruby: foo!/foo? collide with foo in node IDs — the bang/predicate variant is silently dropped

2 participants