Skip to content

Don't let a builtin name collision override a callable's real signature - #83

Merged
thorwhalen merged 1 commit into
masterfrom
claude/builtin-name-sig-collision
Aug 4, 2026
Merged

Don't let a builtin name collision override a callable's real signature#83
thorwhalen merged 1 commit into
masterfrom
claude/builtin-name-sig-collision

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

The bug

_robust_signature_of_callable consults the curated sigs_for_sigless_builtin_name /
sigs_for_type_name tables before trying inspect.signature. Those tables are keyed
by __name__ (and by type name), which is only a sound key for the C-level builtins they
were written for. Any callable that merely shares a builtin's name gets handed the
builtin's signature instead of its own:

from meshed.util import mk_place_holder_func
f = mk_place_holder_func(['chunker', 'wfs'], name='map')

inspect.signature(f)   # (chunker, wfs)                  <- correct, and explicitly set
Sig(f)                 # (func, iterable, /, *iterables) <- the *builtin* map's signature

A plain def map(a, b): ... is hit too — nothing about the object is consulted, only its name.

Downstream damage

meshed names each DAG node's placeholder function after its func_label, so a user story
containing map(chunker, wfs) produced a node with a phantom third input:

-chunker,wfs -> map -> chks_iter
+chunker,wfs,iterables -> map -> chks_iter

That is the failure currently red on i2mint/meshed#76's CI (and on meshed master since
2025-11-18). It is a genuine defect, not stale expected output.

Where it came from

Commit 0e72e58 ("fix: tests for 3.12") moved the table lookups ahead of inspect.signature
so that operator instances (itemgetter/attrgetter/methodcaller) — which in Python 3.12+ do
have a signature, but a useless generic (*args, **kwargs) — would get their curated ones.
That motivation is legitimate; keying it on __name__ for all callables is not.
Shipped in 0.1.57, still present in 0.1.64.

The fix

Not a demotion to a pure fallback. Moving both lookups after inspect.signature would change
resolution for print, partialmethod, the operator classes and the dunder wrappers, whose
signature succeeds but whose curated entries are intentionally richer (annotations, real
parameter names).

Instead the tables are skipped only for callables that declare a signature of their own
Python-defined functions/methods, and anything carrying an explicit __signature__ (which is
how i2 itself stamps signatures onto functools.partial objects and other wrappers). Genuine
builtins declare neither, so they keep their curated signatures, and the 3.12 operator fix is
preserved.

Verification

  • Behaviour-neutral on builtins: resolution is byte-identical before and after across all
    ~170 callables in builtins, functools and operator, plus itemgetter(1) /
    attrgetter('a') / methodcaller('m') instances. The only diff in the A/B dump was a
    memory address inside a default's repr.
  • i2 suite: 699 passed, 2 xfailed.
  • Dependents sweep (47 packages): identical pass/fail set before and after — 32 pass,
    14 fail, every failure pre-existing and unrelated (missing optional deps, etc.).
  • New regression tests pin both directions and are not vacuous: with the fix reverted,
    test_builtin_name_collision_does_not_override_own_signature fails with exactly
    - (chunker, wfs) / + (func, iterable, /, *iterables).
  • meshed: meshed/makers.py doctests pass on Python 3.10 (the version meshed's CI uses)
    with this change.

Ordering

Independent of #82 — that PR touches deco.py / test_wrapper.py, this one
signatures.py / test_signatures.py. Verified this change does not disturb the
double_up_as_factory feature-detection in meshed's new pin, which still correctly skips on
pre-#82 i2.

Releasing this is what unblocks i2mint/meshed#76, whose install_requires is an unpinned i2.

https://claude.ai/code/session_01Kug7UUbVeCQgruvNXUq63c

`_robust_signature_of_callable` consulted the curated
`sigs_for_sigless_builtin_name` / `sigs_for_type_name` tables *before* trying
`inspect.signature`. Those tables are keyed by `__name__` (and by type name),
which is only a sound key for the C-level builtins they were written for. Any
callable that merely shared a builtin's name was therefore handed the builtin's
signature instead of its own:

    f = mk_place_holder_func(['chunker', 'wfs'], name='map')
    inspect.signature(f)  # (chunker, wfs)                 <- correct
    Sig(f)                # (func, iterable, /, *iterables) <- wrong

Downstream this grew phantom parameters: every meshed DAG node built from a
function named `map` sprouted an extra `iterables` input.

The name-before-signature order was introduced to fix `operator` instances
(itemgetter/attrgetter/methodcaller), which in Python 3.12+ do have a signature
but a useless generic `(*args, **kwargs)`. That part is legitimate, so rather
than demote the tables to a pure fallback (which would change resolution for
`print`, `partialmethod`, the operator classes and the dunder wrappers, whose
`signature` succeeds but whose curated entries are intentionally richer), the
tables are now skipped only for callables that declare a signature of their own
-- Python-defined functions/methods, and anything carrying an explicit
`__signature__`. Genuine builtins declare neither, so they are unaffected.

Verified behaviour-neutral: resolution is byte-identical before and after across
all ~170 callables in `builtins`, `functools` and `operator` plus operator
instances. i2's own suite passes (699), and the 47-package dependents sweep has
an identical pass/fail set before and after (32 pass, 14 fail, all pre-existing).

Claude-Session: https://claude.ai/code/session_01Kug7UUbVeCQgruvNXUq63c
@thorwhalen
thorwhalen merged commit 55cba60 into master Aug 4, 2026
8 checks passed
@thorwhalen
thorwhalen deleted the claude/builtin-name-sig-collision branch August 4, 2026 13:41
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.

1 participant