Skip to content

fix(registry): refuse a name-only match the receiver chain contradicts - #1897

Open
CaptainMittens wants to merge 1 commit into
DeusData:mainfrom
CaptainMittens:fix/resolve-receiver-chain-mismatch
Open

fix(registry): refuse a name-only match the receiver chain contradicts#1897
CaptainMittens wants to merge 1 commit into
DeusData:mainfrom
CaptainMittens:fix/resolve-receiver-chain-mismatch

Conversation

@CaptainMittens

Copy link
Copy Markdown
Contributor

What does this PR do?

A Swift project's URLSession.shared.data bound to its own PickedFile.data,
UserDefaults.standard.string to a DTO's string, and JSONEncoder().encode
to a model's encode. "Who calls this?" then answers with calls that never
happened.

The cause is that the two name-only resolution strategies match on the final
segment alone. A dotted callee whose first segment starts upper-case names a
type — URLSession, Calendar, JSONEncoder — and that receiver chain is
evidence the scorers were discarding.

receiver_chain_admits() now requires the candidate's own parent segment to
appear somewhere in that chain:

callee candidate in the chain? outcome
URLSession.shared.data HomeboxUI.PickedFile.data PickedFile — no refused
Calendar.utcGregorian.startOfDayUTC AuthDTOs.Calendar.startOfDayUTC Calendar — yes kept

Three shapes pass through untouched, so ordinary code is unaffected:

  • a callee with no separator, which has no chain to judge;
  • a lower-case root, which names a value whose declared type the chain does
    not show — vm.load, http.Get, os.path.join;
  • a name in capitals with an underscore, which is a constant holding a value
    rather than a type. This carve-out is measured, not guessed: without it the
    gate refused ISO_4217_URL.lowerbuiltins.str.lower, which is correct.
    JSON and URL carry no underscore and stay guarded.

The gate applies only at the two name-only exits of resolve_name_lookup.
import_map, same_module and qualified_suffix already carry real evidence
and are left alone.

Language agnostic by design — the registry holds no language, and every language
that writes receiver chains gains the same protection.

Why the confidence score could not do this instead

The clearest errors were the highest-confidence ones. Swift's import map is
empty (system frameworks are never indexed nodes, so cbm_pxc_build_import_map
drops them), and the 0.5 penalty in resolve_name_lookup only applies when an
import map exists. So the worst calls kept the full 0.75. A threshold would have
cut correct edges and left these.

A "never point a call at a Variable" guard is also wrong: Variable is a legal
target on purpose (cbm_label_is_registry_symbol), one project here has 645 such
edges and another 984, and Variable nodes carry no type property, so nothing
in the graph separates a callable variable from stored Data.

Fixes #1893

Validation

Reproduce-first. With the fix reverted and the five tests kept, exactly the two
refusal tests fail and the three guard tests already pass — which is the point,
since a guard test that only passes with the change proves nothing:

registry_receiver_chain_refuses_library_unique_name_issue1893   FAIL tests/test_pipeline.c:8799: r.qualified_name is not NULL
registry_receiver_chain_refuses_library_suffix_match_issue1893  FAIL tests/test_pipeline.c:8814: r.qualified_name is not NULL
registry_receiver_chain_keeps_project_extension_issue1893       PASS
registry_receiver_chain_ignores_lowercase_root_issue1893        PASS
registry_receiver_chain_ignores_bare_name_issue1893             PASS

Blast radius, measured on three real codebases

The gate changes resolution for every language, so I counted what it refuses
rather than reasoning about it. Read from live graphs, applying the rule to every
unique_name and suffix_match edge:

project languages unique_name refused suffix_match refused
homebox-interface Swift 5300 278 (5.2%) 3827 295 (7.7%)
homebox Go, TypeScript, Python 4933 14 (0.3%) 3348 2 (0.06%)
Home_Server Python, shell 190 2 (1.1%) 37 0

The Swift project is where the bug lives and it moves most. The Go/TypeScript
project loses 16 edges out of 8281 name-only ones — 0.2% — because its import
map already resolves most calls before the gate is reached.

I read every non-Swift refusal by hand. They are all wrong bindings the gate
should refuse:

Date.now            -> homebox.scripts.asc-build-state.now
JSON.parse          -> homebox.frontend.lib.datelib.datelib.parse
Array.from          -> homebox.backend.pkgs.mailer.test-mailer-template.from
Promise.all         -> homebox.docs...starlightChangelogs.versions.all
Object.entries      -> homebox.frontend.pages.checkbooks.[id].entries
Body.Close          -> homebox.backend.internal.data.ent.Close
Path(__file__).resolve -> Home_Server.homelab.scripts.withsecret.resolve

import_map, same_module and qualified_suffix counts are unchanged
everywhere, since the gate never runs on those paths.

Suites

Suite Result
pipeline 260 passed, 0 failed (255 before, plus the 5 new)
full make -f Makefile.cbm test 7635 passed, 28 failed, 8 skipped

The 28 failures are all in tests/test_cli.c (client install/uninstall) and are
pre-existing in my environment. Measured rather than assumed: origin/main here
runs 7630 passed / 28 failed, and this branch adds exactly the 5 new tests with
the same 28 failures in the same file. If they are green on your runners they
are environmental on mine.

Lint: make -f Makefile.cbm lint-cppcheck and lint-no-suppress both exit 0.
clang-format wants no change on any line this PR adds. I could not run
clang-tidy locally — it is not in my toolchain — so that one is unverified on
my side.

Checklist

  • Every commit is signed off (git commit -s) — required, CI rejects
    unsigned commits (DCO, see CONTRIBUTING.md)
  • Tests pass locally (make -f Makefile.cbm test)
  • Lint passes (make -f Makefile.cbm lint-ci)
  • New behavior is covered by a test (reproduce-first for bug fixes)

A dotted callee whose first segment starts upper-case names a type. That
receiver chain is evidence, and the two name-only strategies threw it away:
they matched on the final segment alone, so Foundation's
URLSession.shared.data bound to a project's own PickedFile.data — at 0.75
confidence, with nothing in the graph to show the answer was wrong.

receiver_chain_admits() now requires the candidate's own parent segment to
appear somewhere in that chain. Calendar.utcGregorian.startOfDayUTC still
resolves to AuthDTOs.Calendar.startOfDayUTC, because the project really does
extend Calendar and Calendar is in the chain.

Three shapes pass through untouched:

  - a callee with no separator, which has no chain to judge;
  - a lower-case root, which names a value whose declared type the chain does
    not show (vm.load, http.Get, os.path.join);
  - a name in capitals with underscores, which is a constant holding a value
    rather than a type. Measured: without this carve-out the gate refused
    ISO_4217_URL.lower -> builtins.str.lower, which is correct. JSON and URL
    carry no underscore and stay guarded.

The gate applies only at the two name-only exits of resolve_name_lookup.
import_map, same_module and qualified_suffix already carry real evidence and
are left alone.

Language agnostic by design: the registry holds no language, and every
language that writes receiver chains gains the same protection.

Fixes DeusData#1893

Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

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

Labels

None yet

Projects

None yet

1 participant