typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

Add `__call__` attribute to `function`

Open LeeeeT opened this issue 1 year ago • 5 comments

I guess the lack of this attribute causes mypy and pyright to emit a false positive error for the following code.

def a(): pass
a.__call__

mypy:

file.py:2: error: "Callable[[], Any]" not callable  [operator]

pyright:

file.py
  file.py:2:3 - error: Cannot access attribute "__call__" for class "function"
    Attribute "__call__" is unknown (reportFunctionMemberAccess)

LeeeeT avatar Aug 22 '24 16:08 LeeeeT

Diff from mypy_primer, showing the effect of this PR on open source code:

discord.py (https://github.com/Rapptz/discord.py)
- discord/backoff.py:75: error: Incompatible types in assignment (expression has type "function", variable has type "Callable[..., int | float]")  [assignment]

schemathesis (https://github.com/schemathesis/schemathesis)
+ src/schemathesis/service/extensions.py:182: error: Unused "type: ignore" comment  [unused-ignore]
+ src/schemathesis/specs/openapi/_hypothesis.py: note: At top level:
+ src/schemathesis/specs/openapi/_hypothesis.py:422: error: Unused "type: ignore" comment  [unused-ignore]
+ src/schemathesis/cli/__init__.py: note: At top level:
+ src/schemathesis/cli/__init__.py:1472: error: Unused "type: ignore" comment  [unused-ignore]
+ src/schemathesis/cli/__init__.py:1477: error: Unused "type: ignore" comment  [unused-ignore]
+ src/schemathesis/cli/__init__.py:1478: error: Unused "type: ignore" comment  [unused-ignore]

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/computation/ops.py:523: error: Unused "type: ignore" comment  [unused-ignore]

ignite (https://github.com/pytorch/ignite)
+ ignite/metrics/fbeta.py:154: error: Unused "type: ignore" comment  [unused-ignore]
+ ignite/metrics/fbeta.py:163: error: Unused "type: ignore" comment  [unused-ignore]

sphinx (https://github.com/sphinx-doc/sphinx)
+ sphinx/directives/__init__.py:327: error: Unused "type: ignore" comment  [unused-ignore]
+ sphinx/util/rst.py: note: At top level:
+ sphinx/util/rst.py:69: error: Unused "type: ignore" comment  [unused-ignore]

pandera (https://github.com/pandera-dev/pandera)
+ tests/strategies/test_strategies.py:1010: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/core/test_decorators.py:429: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/core/test_decorators.py:442: error: Unused "type: ignore" comment  [unused-ignore]

trio (https://github.com/python-trio/trio)
+ src/trio/_core/_ki.py:199: error: Unused "type: ignore" comment  [unused-ignore]
+ src/trio/_core/_ki.py:202: error: Unused "type: ignore" comment  [unused-ignore]

dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ ddtrace/_trace/utils_redis.py:34: error: Unused "type: ignore" comment  [unused-ignore]

scrapy (https://github.com/scrapy/scrapy)
+ scrapy/extensions/httpcache.py:324: error: Unused "type: ignore" comment  [unused-ignore]

werkzeug (https://github.com/pallets/werkzeug)
- tests/test_formparser.py:436: error: Argument "stream_factory" to "MultiPartParser" has incompatible type "Callable[[], Any]"; expected "TStreamFactory | None"  [arg-type]
- tests/test_formparser.py:438: error: Non-overlapping identity check (left operand type: "TStreamFactory", right operand type: "Callable[[], Any]")  [comparison-overlap]

jax (https://github.com/google/jax)
+ jax/_src/core.py:3227: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/pallas/mosaic/random.py:158: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/pallas/mosaic/random.py:159: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/pallas/mosaic/random.py:160: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/experimental/jax2tf/tests/shape_poly_test.py:2219: error: Unused "type: ignore" comment  [unused-ignore]

graphql-core (https://github.com/graphql-python/graphql-core)
+ src/graphql/execution/execute.py:554: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/execution/test_abstract.py:44: error: Unused "type: ignore" comment  [unused-ignore]

black (https://github.com/psf/black)
- src/blackd/__init__.py:91:22: error: List item 0 has incompatible type "Callable[[Request, Callable[[Request], Awaitable[StreamResponse]]], Awaitable[StreamResponse]]"; expected "Middleware"  [list-item]

starlette (https://github.com/encode/starlette)
+ tests/test_applications.py:128: error: Unused "type: ignore" comment  [unused-ignore]

dragonchain (https://github.com/dragonchain/dragonchain)
- dragonchain/job_processor/contract_job.py:307:24: error: Cannot call function of unknown type  [operator]
- dragonchain/job_processor/contract_job.py:344:20: error: Cannot call function of unknown type  [operator]

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/cli/profile.py:177: error: Cannot call function of unknown type  [operator]

antidote (https://github.com/Finistere/antidote)
+ tests/lib/interface/test_custom.py:434: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/lib/interface/test_custom.py:437: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/lib/interface/test_custom.py:440: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/lib/interface/test_custom.py:443: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/lib/interface/test_custom.py:446: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/lib/interface/test_custom.py:449: error: Unused "type: ignore" comment  [unused-ignore]

github-actions[bot] avatar Aug 22 '24 17:08 github-actions[bot]

This looks completely reasonable to me, suspiciously so, in fact. And the primer hits seem to agree, although I haven't looked at the "red" hits in detail. I wonder whether there was any reason we didn't do this earlier? Maybe it's related to the fact that this is a "fake" type that seems to mainly exist for the benefit of mypy? Still, at first glance this looks like a sensible change.

srittau avatar Aug 22 '24 17:08 srittau

I'm not sure this is right. I suspect it simply makes mypy accept a lot more code because functions now get signature (*Any, **Any) -> Any more easily.

JelleZijlstra avatar Aug 22 '24 17:08 JelleZijlstra

Yeah... if you look at e.g. https://github.com/pandas-dev/pandas/blob/59bb3f44a9bdd6d79704caab4d5c4e229945aefd/pandas/core/computation/ops.py#L522-L523 (one of the errors going away here), it's clear that mypy already knows that it has an object of type function there, and it's clear that it knows that the function object is in fact callable. So ISTM that it's very much a deliberate choice on the part of mypy to emit an error there (because it doesn't know the signature of the function being called); but if we make this change, it will no longer be able to emit that error, because it'll find the (*Any, **Any) -> Any signature in the stub before it's able to fallback to the hook it has for builtins.function and emit the proper error message

AlexWaygood avatar Aug 22 '24 17:08 AlexWaygood

builtins.function is a legacy thing that mypy needs for historical reasons, see https://github.com/python/mypy/issues/8240. Usually it uses Callable or some other class with a __call__. My guess is that the "Cannot call function of unknown type" made sense at the time it was implemented, but IMO it doesn't fit our "prefer false negatives" philosophy. If we can somehow make builtins.function less painful to users, why not?

The pyright error does not make sense to me. Perhaps "function" in the pyright error refers to something else than builtins.function.

Akuli avatar Aug 27 '24 15:08 Akuli

Closing as I don't think this makes mypy more useful. In the long term we should get rid of builtins.function from the stub (and I would recommend all type checkers to move away from relying on it).

JelleZijlstra avatar Oct 31 '24 15:10 JelleZijlstra

Closing as I don't think this makes mypy more useful. In the long term we should get rid of builtins.function from the stub (and I would recommend all type checkers to move away from relying on it).

Maybe we should just remove it? As far as I understand, at least mypy patches typeshed anyway, and this could be another incentive to remove reliance on this type.

srittau avatar Oct 31 '24 15:10 srittau

That feels a bit rude to do unilaterally, I'd rather see this fixed in mypy first. (And I'm not sure if other type checkers also rely on builtins.function.)

JelleZijlstra avatar Oct 31 '24 16:10 JelleZijlstra