python / mypy

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

Subclass default overrides more general baseclass type hint #11897

Open JacobHayes opened 2 years ago

JacobHayes commented 2 years ago

Bug Report

When a base class field has a general type hint (eg: Union[int, str] or a base class) and a subclass sets a default without a type hint, the field's type hint is narrowed to the specific default value's type rather than maintaining the original base class's hint. This prevents a second-order subclass from setting a new default with a different type that would otherwise align with the originally specified hint on the first class.

This is particularly relevant when using something like dataclasses or pydantic.

To Reproduce

from typing import Union

class A:
    x: Union[int, str] = 5

class B(A):
    x = 5

class C(B):
    # error: Incompatible types in assignment (expression has type "str", base class "B" defined the type as "int")
    x = "5"

class RefA:
    x: A

class RefB(RefA):
    x = B()

class RefC(RefB):
    # error: Incompatible types in assignment (expression has type "A", base class "RefB" defined the type as "B")
    x = A()

Expected Behavior

The B.x and RefB.x fields to maintain their base classes type hint rather than implicitly narrow to the default value's type. Thus, C.x and RefC.x fields to support values matching the first base class's type hint.

Actual Behavior

The middle subclasses implicitly narrow the type hint to type(default_value) so a new default on a third subclass must match that, rather than the original explicit hint.

Your Environment

A5rocks commented 2 years ago

I think the problem here is that narrowing is desired in some cases, and undesired in others. For instance:

class Something:
  thing: Sequence[int]

class SomethingMutable:
  thing = [42]  # (we want list[int] here because mutable)

I guess the real question is whether we should require explicit type annotations when narrowing or when staying the same type (currently the default). When framed this way, this becomes a feature request :P

For what it's worth though, I agree with you that it should probably require explicit type annotations when narrowing, but I'm certain there's a pathological case I have to yet to think of where this is very annoying to do and a terrible default.