python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.48k stars 2.83k forks source link

(🐞) `type.__call__` not checked properly #17316

Open KotlinIsland opened 5 months ago

KotlinIsland commented 5 months ago
class A:
    def __init__(self, x: int):
        print(x)

A.__call__("a")  # no error
A("a")  # Argument 1 to "A" has incompatible type "str"; expected "int"  [arg-type]

The typing spec says:

At runtime, a call to a class’ constructor typically results in the invocation of three methods in the following order:

  1. The __call__ method of the metaclass (which is typically supplied by the type class but can be overridden by a custom metaclass and which is responsible for calling the next two methods)

So mypy is checking that usages of the call syntax (()) are conforming to the constructor type, but the functionally identical call attribute __call__. This is against the spec, which specifies that type checkers should mirror the behavior that __call__ will invoke __new__ and __init__

erictraut commented 5 months ago

The type class in the builtins.pyi stub file is defined as:

class type:
     ...
    def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ...
     ...
    def __call__(self, *args: Any, **kwds: Any) -> Any: ...

Based on this definition, mypy is working correctly. Not surprisingly, its behavior matches the other major type checkers here.

If you think that typeshed definition is incorrect or could be improved, you could file a bug report or PR in the typeshed project.

KotlinIsland commented 5 months ago

if these type checkers were just following the stubs, then I would expect A("a") to behave identically to A.__call__("a") and not show an error. The truth is that currently type checkers are special casing type.__call__, but only the operator form, and not the attribute form.

erictraut commented 5 months ago

The truth is that currently type checkers are special casing type.call, but only the operator form, and not the attribute form.

Yes, that's conformant with the recently accepted typing spec chapter on constructor call validation. Mypy is doing the right thing here according to the typing spec, and so are the other type checkers.

If you would like to suggest a change to the typing spec, the typing forum would be a good place to have that discussion.

KotlinIsland commented 5 months ago

Type checkers should mirror this runtime behavior when analyzing a constructor call.

Type checkers are not mirroring this runtime behavior correctly in this case.

That spec should be updated to be more explicit about how to mirror the runtime behavior (to match this case), the type-checkers should be updated to match the runtime behaviour, and typeshed could be updated to have the correct type.