Skip to content

_LANGUAGE_BUILTIN_GLOBALS deletes member calls named after another language's builtins (all grammars) #3381

Description

@Nico8324

Summary

_LANGUAGE_BUILTIN_GLOBALS (extractors/base.py) is one union across every language. The gate that
consults it decides both whether a call gets a same-file bare-name edge and whether it is
recorded in raw_calls — the only route to the receiver-typed resolvers. The union is right for a
bare call (String(x) really would become a god node), but applied to a member call it deletes
the call outright in whatever language does not own the name, with no warning.

open and set are Python builtins; Set, Map, URL, String are JavaScript ones. So
session.open() in Swift, xhr.open() in JavaScript and _server.Set() in C# are all silently
discarded.

Reproduction

// probe.swift
enum T { static func run() { p.open() } }        // 0 calls extracted
enum T { static func run() { p.doWorkXyz() } }   // 1 call  extracted

Measured on graphify 0.9.54, every grammar installed locally. Each probe is compared against the
same source with the callee renamed to a name no builtin table claims:

grammar probe probe calls control calls
python, go, rust, java, kotlin, ruby, php, scala, cpp, swift p.open() 0 1
javascript, typescript xhr.open() 1 2
c_sharp _server.Set() 0 1

13 of 13 drop the call. On one real 338-file Swift codebase this removed 236 member calls,
including AppleReadyStreamFinder.find -> RemuxPlayback.open, which left two tightly coupled
subsystems in separate Louvain communities with no path between them except through Logger.

Suggested fix

A member call carries a receiver, so it is not the ambiguous case the union guards: every
receiver-typed resolver only emits an edge when the receiver's type resolves to exactly one
definition, and the shared cross-file pass drops member calls outright (#543/#1219). Letting a
builtin-named member call through to raw_calls — while forcing tgt_nid = None so it never takes
the unguarded bare-name path — can therefore only add guarded edges.

_builtin_member_call = (
    callee_name is not None
    and is_member_call
    and callee_name in _LANGUAGE_BUILTIN_GLOBALS
)
if callee_name and (
    _builtin_member_call or callee_name not in _LANGUAGE_BUILTIN_GLOBALS
):
    ...
    # and in the defer condition:
    if _python_defer or _java_defer or _builtin_member_call or (...):
        tgt_nid = None

The same gate is duplicated in extractors/go.py (~L480) and extractors/rust.py (~L372) and needs
the same change; in Go, tgt_nid also has to fold _builtin_member_call into the existing
import_path guard.

Verification

With the change applied, all 13 grammars keep the call, and graphify's own 86-file Python source
tree extracts byte-identically
— 2441 nodes / 5624 edges / 2033 calls, with the same top call-target
in-degrees (91 / 56 / 45 / 35 / 27 / 21). No god-node inflation.

Happy to open a PR if the approach looks right.

Activity

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