Skip to content

Current definition of MutableMapping.update makes it impossible to override update in a UserDict #15310

@iFreilicht

Description

@iFreilicht

MutableMapping.update is currently defined like this:

    @overload
    def update(self, m: SupportsKeysAndGetItem[_KT, _VT], /) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, _VT], m: SupportsKeysAndGetItem[str, _VT], /, **kwargs: _VT) -> None: ...
    @overload
    def update(self, m: Iterable[tuple[_KT, _VT]], /) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, _VT], m: Iterable[tuple[str, _VT]], /, **kwargs: _VT) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, _VT], **kwargs: _VT) -> None: ...

Because UserDict inherits from MutableMapping and because the last overload doesn't specify self as a positional-only parameter, this override makes it impossible to override update. I can either try to do this:

class MyDict[K, V](UserDict[K, V]):
    class __EmptyArg:
        pass
    __emptyarg = EmptyArg()

    @overload
    def update(self, m: SupportsKeysAndGetItem[K, V], /) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, V], m: SupportsKeysAndGetItem[str, V], /, **kwargs: V) -> None: ...
    @overload
    def update(self, m: Iterable[tuple[K, V]], /) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, V], m: Iterable[tuple[str, V]], /, **kwargs: V) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, V], **kwargs: V) -> None: ...
    def update(
        self,
        m: SupportsKeysAndGetItem[K, V] | Iterable[tuple[K, V] | __EmptyArg = __emptyarg,
        /,
        **kwargs: V,
    ):
        if isinstance(m, MyDict.__EmptyArg):
            return super().update(**kwargs)
        return super().update(m, **kwargs)

Which pyright faults with the following message:

Overloaded implementation is not consistent with signature of overload 5
  Type "(self: Self@MyDict[K@MyDict, V@MyDict], m: SupportsKeysAndGetItem[K@MyDict, V@MyDict] | Iterable[tuple[K@MyDict, V@MyDict]] | __EmptyArg = __emptyarg, /, **kwargs: V@MyDict) -> None" is not assignable to type "(self: Self@MyDict[K@MyDict, V@MyDict], **kwargs: V@MyDict) -> None"
    Position-only parameter mismatch; parameter "self" is not position-only
    Position-only parameter mismatch; expected 1 but received 0

To "fix this", I would have to update the last overload in my class to this:

    @overload
    def update(self: SupportsGetItem[str, V], /, **kwargs: V) -> None: ...

But that's also incompatible with the definition in typeshed, so of course I get the following error:

Method "update" overrides class "MutableMapping" in an incompatible manner
  Override does not handle all overloads of base method

Potential solution

I updated this line in my local checkout of typeshed to make self positional-only in all cases:

    def update(self: SupportsGetItem[str, _VT], /, **kwargs: _VT) -> None: ...

This resolved the issue, but I might be missing some important detail that makes this not feasible.

In general it seems to me like having multiple overloads in a function where the same parameter is sometimes marked positional-only and sometimes not should be illegal outright, but that's something for the type checkers to validate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions