Don't let a builtin name collision override a callable's real signature - #83
Merged
Merged
Conversation
`_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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
_robust_signature_of_callableconsults the curatedsigs_for_sigless_builtin_name/sigs_for_type_nametables before tryinginspect.signature. Those tables are keyedby
__name__(and by type name), which is only a sound key for the C-level builtins theywere written for. Any callable that merely shares a builtin's name gets handed the
builtin's signature instead of its own:
A plain
def map(a, b): ...is hit too — nothing about the object is consulted, only its name.Downstream damage
meshednames each DAG node's placeholder function after itsfunc_label, so a user storycontaining
map(chunker, wfs)produced a node with a phantom third input:That is the failure currently red on i2mint/meshed#76's CI (and on meshed
mastersince2025-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 ofinspect.signatureso that
operatorinstances (itemgetter/attrgetter/methodcaller) — which in Python 3.12+ dohave 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.signaturewould changeresolution for
print,partialmethod, the operator classes and the dunder wrappers, whosesignaturesucceeds but whose curated entries are intentionally richer (annotations, realparameter 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 ishow i2 itself stamps signatures onto
functools.partialobjects and other wrappers). Genuinebuiltins declare neither, so they keep their curated signatures, and the 3.12
operatorfix ispreserved.
Verification
~170 callables in
builtins,functoolsandoperator, plusitemgetter(1)/attrgetter('a')/methodcaller('m')instances. The only diff in the A/B dump was amemory address inside a default's repr.
14 fail, every failure pre-existing and unrelated (missing optional deps, etc.).
test_builtin_name_collision_does_not_override_own_signaturefails with exactly- (chunker, wfs)/+ (func, iterable, /, *iterables).meshed/makers.pydoctests 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 onesignatures.py/test_signatures.py. Verified this change does not disturb thedouble_up_as_factoryfeature-detection in meshed's new pin, which still correctly skips onpre-#82 i2.
Releasing this is what unblocks i2mint/meshed#76, whose
install_requiresis an unpinnedi2.https://claude.ai/code/session_01Kug7UUbVeCQgruvNXUq63c