Skip to content

Ambiguous overloads for decorators #393

@sk-

Description

@sk-

Describe the bug
The stubs added in commit 09dd6fe have a problem of being ambiguous for the decorators. The issue stem because both overloads set the info argument with a different type, but with a default value.

This is problematic as the spec mentions

If the return types are not equivalent, overload matching is ambiguous. In this case, assume a return type of Any and stop.

Unfortunately, most type checkers don't follow this rule an instead return the first match. However, in this proposal they mention explicitly in Change number 2 that ambiguous overloads are problematic.

The fix is simple and just require removing the default from the first overload. It just require changing it to

@overload
def cached(
    cache: MutableMapping[_KT, Any] | None,
    key: Callable[..., _KT] = ...,
    lock: AbstractContextManager[Any] | None = None,
    condition: _AbstractCondition | None = None,
    info: Literal[True], # note how we removed ' = ...'
) -> Callable[[Callable[..., _R]], _cached_wrapper_info[_R]]: ...

@overload
def cachedmethod(
    cache: Callable[[Any], MutableMapping[_KT, Any]],
    key: Callable[..., _KT] = ...,
    lock: Callable[[Any], AbstractContextManager[Any]] | None = None,
    condition: Callable[[Any], _AbstractCondition] | None = None,
    info: Literal[True], # note how we removed ' = ...'
) -> Callable[[Callable[..., _R]], _cachedmethod_wrapper_info[_R]]: ...

Expected result
All type checkers (specially those that follow the spec carefully) should correctly type check decorators.

Actual result
zuban treats decorated functions as Any (which is correct according to the spec).

Reproduction steps
The following code shows how the code is not correctly typed when using zuban

from typing import reveal_type

import cachetools

@cachetools.cached(cachetools.LRUCache(1))
def foo() -> str:
    return "foo"

reveal_type(foo)

class A:
    def __init__(self):
        self.cache = cachetools.LRUCache(1)

    @cachetools.cachedmethod(lambda self: self.cache)
    def foo(self) -> str:
        return "foo"

reveal_type(A.foo)
uvx zuban check test_decorator.py
test_decorator.py:9: note: Revealed type is "Any"
test_decorator.py:19: note: Revealed type is "Any"

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions