python / python/mypy

`functools.partial` plugin is only triggered on calls

Open
#17,620 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-plugins
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report

partial support is too fragile and easy to fool.

To Reproduce

import typing as t
from functools import partial

# 1.
# uncomment this to get type error
# partial(lambda x, y: x + y, 10)()

# 2.
# But this silly little thing passes type-check
silly: t.Callable[[], int] = partial(lambda x, y: x + y, 10)
silly()

Expected Behavior

Fail to coerce partial into incompatible callable type.

Actual Behavior

Success: no issues found in 1 source file

Your Environment

  • Mypy version used: master
  • Mypy command-line flags: no
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.12

Looks like we carry information about needed arguments somewhere anyways, since 1 fails type check due to not enough arguments, so why we allow to coerce that partial into incompatible callable?
Perhaps this is due to this definition of partial:

    def __call__(self, /, *args: Any, **kwargs: Any) -> _T: ...

But, for example, pyright takes that into account and fails as expected:

[nix-shell:/tmp]$ pyright issue.py
/tmp/issue.py
  /tmp/issue.py:10:30 - error: Expression of type "partial[Unknown]" is incompatible with declared type "() -> int"
    Type "partial[Unknown]" is incompatible with type "() -> int"
      Extra parameter "y" (reportAssignmentType)
1 error, 0 warnings, 0 informations

I think it worth fixing, since we support partial and it is useful to rely on that support. It can be fixed, for example, as viewing partial result internally not as partial[int] type, but as something like

class FakePartial(Protocol[P, Ret]):
    @property
    def args(self) -> tuple[Any, ...]: ...
    
    @property
    def keywords(self) -> dict[any, ...]: ...

    @property
    def func(self) -> t.Callable[..., Ret]: ...

    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Ret: ...

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the two examples using Python 3.12 and mypy master, then compare the behavior with the typeshed functools.pyi definition linked in the issue. Trace the existing functools.partial support and its argument information. Done means the incompatible assignment to a zero-argument callable is rejected while valid partial calls continue to type-check.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.