-- Type checker test cases for abstract classes.


-- Subtyping with abstract classes
-- -------------------------------


[case testAbstractClassSubclasses]

from abc import abstractmethod, ABCMeta

i = None # type: I
j = None # type: J
a = None # type: A
b = None # type: B
c = None # type: C

j = c  # E: Incompatible types in assignment (expression has type "C", variable has type "J")
a = i  # E: Incompatible types in assignment (expression has type "I", variable has type "A")
a = j  # E: Incompatible types in assignment (expression has type "J", variable has type "A")
b = i  # E: Incompatible types in assignment (expression has type "I", variable has type "B")

i = a
i = b
i = c
j = a
j = b
a = b

class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self): pass
class J(metaclass=ABCMeta):
    @abstractmethod
    def g(self): pass
class A(I, J): pass
class B(A): pass
class C(I): pass

[case testAbstractClassSubtypingViaExtension]

from abc import abstractmethod, ABCMeta

i = None # type: I
j = None # type: J
a = None # type: A
o = None # type: object

j = i # E: Incompatible types in assignment (expression has type "I", variable has type "J")
a = i # E: Incompatible types in assignment (expression has type "I", variable has type "A")
a = j # E: Incompatible types in assignment (expression has type "J", variable has type "A")
i = o # E: Incompatible types in assignment (expression has type "object", variable has type "I")
j = o # E: Incompatible types in assignment (expression has type "object", variable has type "J")

i = a
j = a
i = j
o = i
o = j

class I(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass
class J(I): pass
class A(J): pass

[case testInheritingAbstractClassInSubclass]

from abc import abstractmethod, ABCMeta

i = None # type: I
a = None # type: A
b = None # type: B

i = a # E: Incompatible types in assignment (expression has type "A", variable has type "I")
b = a # E: Incompatible types in assignment (expression has type "A", variable has type "B")

a = b
i = b

class I(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass
class A: pass
class B(A, I): pass


-- Abstract class objects
-- ----------------------


[case testAbstractClassAsTypeObject]

from abc import abstractmethod, ABCMeta

o = None # type: object
t = None # type: type

o = I
t = I

class I(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass

[case testAbstractClassInCasts]
from typing import cast
from abc import abstractmethod, ABCMeta

class I(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass
class A(I): pass
class B: pass

i, a, b = None, None, None # type: (I, A, B)
o = None # type: object

a = cast(I, o) # E: Incompatible types in assignment (expression has type "I", variable has type "A")
b = cast(B, i) # Ok; a subclass of B might inherit I
i = cast(I, b) # Ok; a subclass of B might inherit I

i = cast(I, o)
i = cast(I, a)

[case testInstantiatingClassThatImplementsAbstractMethod]
from abc import abstractmethod, ABCMeta
import typing
class A(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass
class B(A):
  def f(self): pass
B()
[out]

[case testInstantiatingAbstractClass]
from abc import abstractmethod, ABCMeta
import typing
class A(metaclass=ABCMeta): pass
class B(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass
A() # OK
B() # E: Cannot instantiate abstract class 'B' with abstract attribute 'f'
[out]

[case testInstantiatingClassWithInheritedAbstractMethod]
from abc import abstractmethod, ABCMeta
import typing
class A(metaclass=ABCMeta):
  @abstractmethod
  def f(self): pass
  @abstractmethod
  def g(self): pass
class B(A): pass
B() # E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g'
[out]

[case testInstantiationAbstractsInTypeForFunctions]
from typing import Type
from abc import abstractmethod

class A:
    @abstractmethod
    def m(self) -> None: pass
class B(A): pass
class C(B):
    def m(self) -> None:
        pass

def f(cls: Type[A]) -> A:
    return cls()  # OK
def g() -> A:
    return A()  # E: Cannot instantiate abstract class 'A' with abstract attribute 'm'

f(A)  # E: Only concrete class can be given where "Type[A]" is expected
f(B)  # E: Only concrete class can be given where "Type[A]" is expected
f(C)  # OK
x: Type[B]
f(x)  # OK
[out]

[case testInstantiationAbstractsInTypeForAliases]
from typing import Type
from abc import abstractmethod

class A:
    @abstractmethod
    def m(self) -> None: pass
class B(A): pass
class C(B):
    def m(self) -> None:
        pass

def f(cls: Type[A]) -> A:
    return cls()  # OK

Alias = A
GoodAlias = C
Alias()  # E: Cannot instantiate abstract class 'A' with abstract attribute 'm'
GoodAlias()
f(Alias)  # E: Only concrete class can be given where "Type[A]" is expected
f(GoodAlias)
[out]

[case testInstantiationAbstractsInTypeForVariables]
from typing import Type
from abc import abstractmethod

class A:
    @abstractmethod
    def m(self) -> None: pass
class B(A): pass
class C(B):
    def m(self) -> None:
        pass

var: Type[A]
var()
var = A # E: Can only assign concrete classes to a variable of type "Type[A]"
var = B # E: Can only assign concrete classes to a variable of type "Type[A]"
var = C # OK

var_old = None # type: Type[A] # Old syntax for variable annotations
var_old()
var_old = A # E: Can only assign concrete classes to a variable of type "Type[A]"
var_old = B # E: Can only assign concrete classes to a variable of type "Type[A]"
var_old = C # OK
[out]

[case testInstantiationAbstractsInTypeForClassMethods]
from typing import Type
from abc import abstractmethod

class Logger:
    @staticmethod
    def log(a: Type[C]):
        pass
class C:
    @classmethod
    def action(cls) -> None:
        cls() #OK for classmethods
        Logger.log(cls)  #OK for classmethods
    @abstractmethod
    def m(self) -> None:
        pass
[builtins fixtures/classmethod.pyi]
[out]

[case testInstantiatingClassWithInheritedAbstractMethodAndSuppression]
from abc import abstractmethod, ABCMeta
import typing
class A(metaclass=ABCMeta):
    @abstractmethod
    def a(self): pass
    @abstractmethod
    def b(self): pass
    @abstractmethod
    def c(self): pass
    @abstractmethod
    def d(self): pass
    @abstractmethod
    def e(self): pass
    @abstractmethod
    def f(self): pass
    @abstractmethod
    def g(self): pass
    @abstractmethod
    def h(self): pass
    @abstractmethod
    def i(self): pass
    @abstractmethod
    def j(self): pass
a = A() # E: Cannot instantiate abstract class 'A' with abstract attributes 'a', 'b', ... and 'j' (7 methods suppressed)
[out]


-- Implementing abstract methods
-- -----------------------------


[case testImplementingAbstractMethod]
from abc import abstractmethod, ABCMeta
import typing
class A(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x: int) -> int: pass
    @abstractmethod
    def g(self, x: int) -> int: pass
class B(A):
    def f(self, x: str) -> int: \
            # E: Argument 1 of "f" incompatible with supertype "A"
        pass
    def g(self, x: int) -> int: pass
[out]

[case testImplementingAbstractMethodWithMultipleBaseClasses]
from abc import abstractmethod, ABCMeta
import typing
class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x: int) -> int: pass
class J(metaclass=ABCMeta):
    @abstractmethod
    def g(self, x: str) -> str: pass
class A(I, J):
    def f(self, x: str) -> int: pass \
        # E: Argument 1 of "f" incompatible with supertype "I"
    def g(self, x: str) -> int: pass \
        # E: Return type of "g" incompatible with supertype "J"
    def h(self) -> int: pass # Not related to any base class
[out]

[case testImplementingAbstractMethodWithExtension]
from abc import abstractmethod, ABCMeta
import typing
class J(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x: int) -> int: pass
class I(J): pass
class A(I):
    def f(self, x: str) -> int: pass \
        # E: Argument 1 of "f" incompatible with supertype "J"
[out]

[case testInvalidOverridingAbstractMethod]
from abc import abstractmethod, ABCMeta
import typing
class J(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x: 'J') -> None: pass
class I(J):
    @abstractmethod
    def f(self, x: 'I') -> None: pass # E: Argument 1 of "f" incompatible with supertype "J"
[out]

[case testAbstractClassCoAndContraVariance]
from abc import abstractmethod, ABCMeta
import typing
class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self, a: A) -> 'I': pass
    @abstractmethod
    def g(self, a: A) -> 'I': pass
    @abstractmethod
    def h(self, a: 'I') -> A: pass
class A(I):
    def h(self, a: 'A') -> 'I': # Fail
        pass
    def f(self, a: 'I') -> 'I':
        pass
    def g(self, a: 'A') -> 'A':
        pass
[out]
main:11: error: Argument 1 of "h" incompatible with supertype "I"
main:11: error: Return type of "h" incompatible with supertype "I"


-- Accessing abstract members
-- --------------------------


[case testAccessingAbstractMethod]

from abc import abstractmethod, ABCMeta

class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self, a: int) -> str: pass

i, a, b = None, None, None # type: (I, int, str)

a = i.f(a) # E: Incompatible types in assignment (expression has type "str", variable has type "int")
b = i.f(b) # E: Argument 1 to "f" of "I" has incompatible type "str"; expected "int"
i.g()      # E: "I" has no attribute "g"

b = i.f(a)

[case testAccessingInheritedAbstractMethod]

from abc import abstractmethod, ABCMeta

class J(metaclass=ABCMeta):
    @abstractmethod
    def f(self, a: int) -> str: pass
class I(J): pass

i, a, b = None, None, None # type: (I, int, str)

a = i.f(1) # E: Incompatible types in assignment (expression has type "str", variable has type "int")
b = i.f(1)


-- Any (dynamic) types
-- -------------------


[case testAbstractClassWithAllDynamicTypes]
from abc import abstractmethod, ABCMeta
import typing
class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x): pass
    @abstractmethod
    def g(self, x): pass
class A(I):
    def f(self, x): pass
    def g(self, x, y) -> None: pass \
        # E: Signature of "g" incompatible with supertype "I"
[out]

[case testAbstractClassWithAllDynamicTypes2]
from abc import abstractmethod, ABCMeta
import typing
class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x): pass
    @abstractmethod
    def g(self, x): pass
class A(I):
    def f(self, x): pass
    def g(self, x, y): pass
[out]

[case testAbstractClassWithImplementationUsingDynamicTypes]
from abc import abstractmethod, ABCMeta
import typing
class I(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x: int) -> None: pass
    @abstractmethod
    def g(self, x: int) -> None: pass
class A(I):
    def f(self, x): pass
    def g(self, x, y): pass
[out]


-- Special cases
-- -------------


[case testMultipleAbstractBases]
from abc import abstractmethod, ABCMeta
import typing
class A(metaclass=ABCMeta):
  @abstractmethod
  def f(self) -> None: pass
class B(metaclass=ABCMeta):
  @abstractmethod
  def g(self) -> None: pass
class C(A, B):
  @abstractmethod
  def h(self) -> None: pass

[case testMemberAccessWithMultipleAbstractBaseClasses]

from abc import abstractmethod, ABCMeta

class A(metaclass=ABCMeta):
    @abstractmethod
    def f(self) -> None: pass
class B(metaclass=ABCMeta):
    @abstractmethod
    def g(self) -> None: pass
class C(A, B): pass
x = None # type: C
x.f()
x.g()
x.f(x) # E: Too many arguments for "f" of "A"
x.g(x) # E: Too many arguments for "g" of "B"

[case testInstantiatingAbstractClassWithMultipleBaseClasses]

from abc import abstractmethod, ABCMeta

class A(metaclass=ABCMeta):
  @abstractmethod
  def f(self) -> None: pass
class B(metaclass=ABCMeta):
  @abstractmethod
  def g(self) -> None: pass
class C(A, B):
  def f(self) -> None: pass
class D(A, B):
  def g(self) -> None: pass
class E(A, B):
  def f(self) -> None: pass
  def g(self) -> None: pass
C() # E: Cannot instantiate abstract class 'C' with abstract attribute 'g'
D() # E: Cannot instantiate abstract class 'D' with abstract attribute 'f'
E()

[case testInconsistentMro]
from abc import abstractmethod, ABCMeta
import typing

class A(metaclass=ABCMeta): pass
class B(object, A): pass \
      # E: Cannot determine consistent method resolution order (MRO) for "B"

[case testOverloadedAbstractMethod]
from foo import *
[file foo.pyi]
from abc import abstractmethod, ABCMeta
from typing import overload

class A(metaclass=ABCMeta):
  @abstractmethod
  @overload
  def f(self, x: int) -> int: pass
  @abstractmethod
  @overload
  def f(self, x: str) -> str: pass

class B(A):
  @overload
  def f(self, x: int) -> int: pass
  @overload
  def f(self, x: str) -> str: pass
A() # E: Cannot instantiate abstract class 'A' with abstract attribute 'f'
B()
B().f(1)
a = B() # type: A
a.f(1)
a.f('')
a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B" \
         # N: Possible overload variants: \
         # N:     def f(self, x: int) -> int \
         # N:     def f(self, x: str) -> str

[case testOverloadedAbstractMethodWithAlternativeDecoratorOrder]
from foo import *
[file foo.pyi]
from abc import abstractmethod, ABCMeta
from typing import overload

class A(metaclass=ABCMeta):
  @overload
  @abstractmethod
  def f(self, x: int) -> int: pass
  @overload
  @abstractmethod
  def f(self, x: str) -> str: pass

class B(A):
  @overload
  def f(self, x: int) -> int: pass
  @overload
  def f(self, x: str) -> str: pass
A() # E: Cannot instantiate abstract class 'A' with abstract attribute 'f'
B()
B().f(1)
a = B() # type: A
a.f(1)
a.f('')
a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B" \
         # N: Possible overload variants: \
         # N:     def f(self, x: int) -> int \
         # N:     def f(self, x: str) -> str

[case testOverloadedAbstractMethodVariantMissingDecorator1]
from foo import *
[file foo.pyi]
from abc import abstractmethod, ABCMeta
from typing import overload

class A(metaclass=ABCMeta):
  @abstractmethod \
    # E: Overloaded method has both abstract and non-abstract variants
  @overload
  def f(self, x: int) -> int: pass
  @overload
  def f(self, x: str) -> str: pass
[out]

[case testOverloadedAbstractMethodVariantMissingDecorator1]
from foo import *
[file foo.pyi]
from abc import abstractmethod, ABCMeta
from typing import overload

class A(metaclass=ABCMeta):
  @overload \
    # E: Overloaded method has both abstract and non-abstract variants
  def f(self, x: int) -> int: pass
  @abstractmethod
  @overload
  def f(self, x: str) -> str: pass
[out]

[case testMultipleInheritanceAndAbstractMethod]
import typing
from abc import abstractmethod, ABCMeta
class A:
  def f(self, x: str) -> None: pass
class B(metaclass=ABCMeta):
  @abstractmethod
  def f(self, x: str) -> None: pass
class C(A, B): pass

[case testMultipleInheritanceAndAbstractMethod2]
import typing
from abc import abstractmethod, ABCMeta
class A:
  def f(self, x: str) -> None: pass
class B(metaclass=ABCMeta):
  @abstractmethod
  def f(self, x: int) -> None: pass
class C(A, B): pass
[out]
main:8: error: Definition of "f" in base class "A" is incompatible with definition in base class "B"

[case testCallAbstractMethodBeforeDefinition]
import typing
from abc import abstractmethod, ABCMeta
class A(metaclass=ABCMeta):
    def f(self) -> None:
        self.g(1) # E: Argument 1 to "g" of "A" has incompatible type "int"; expected "str"
    @abstractmethod
    def g(self, x: str) -> None: pass
[out]

[case testAbstractOperatorMethods1]
import typing
from abc import abstractmethod, ABCMeta
class A(metaclass=ABCMeta):
    @abstractmethod
    def __lt__(self, other: 'A') -> int: pass
    @abstractmethod
    def __gt__(self, other: 'A') -> int: pass

[case testAbstractOperatorMethods2]
import typing
from abc import abstractmethod, ABCMeta
class A(metaclass=ABCMeta):
    @abstractmethod
    def __radd__(self, other: 'C') -> str: pass # Error
class B:
    @abstractmethod
    def __add__(self, other: 'A') -> int: pass
class C:
    def __add__(self, other: int) -> B: pass
[out]

[case testAbstractClassWithAnyBase]
from typing import Any
from abc import abstractmethod, ABCMeta

A: Any

class D(metaclass=ABCMeta):
    @abstractmethod
    def f(self) -> None: pass

class C(A, D):
    pass

C()  # A might implement 'f'


-- Abstract properties
-- -------------------


[case testReadOnlyAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
def f(a: A) -> None:
    a.x() # E: "int" not callable
    a.x = 1  # E: Property "x" defined in "A" is read-only
[out]

[case testReadOnlyAbstractPropertyForwardRef]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
    a.x() # E: "int" not callable
    a.x = 1  # E: Property "x" defined in "A" is read-only
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
[out]

[case testReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
    a.x.y # E: "int" has no attribute "y"
    a.x = 1
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
    @x.setter
    def x(self, x: int) -> None: pass
[out]

[case testInstantiateClassWithReadOnlyAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
class B(A): pass
b = B() # E: Cannot instantiate abstract class 'B' with abstract attribute 'x'

[case testInstantiateClassWithReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
    @x.setter
    def x(self, x: int) -> None: pass
class B(A): pass
b = B() # E: Cannot instantiate abstract class 'B' with abstract attribute 'x'

[case testImplementAbstractPropertyViaProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
class B(A):
    @property
    def x(self) -> int: pass
b = B()
b.x() # E: "int" not callable
[builtins fixtures/property.pyi]

[case testImplementReradWriteAbstractPropertyViaProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
    @x.setter
    def x(self, v: int) -> None: pass
class B(A):
    @property
    def x(self) -> int: pass
    @x.setter
    def x(self, v: int) -> None: pass
b = B()
b.x.y # E: "int" has no attribute "y"
[builtins fixtures/property.pyi]

[case testImplementAbstractPropertyViaPropertyInvalidType]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
class B(A):
    @property
    def x(self) -> str: pass # E: Return type of "x" incompatible with supertype "A"
b = B()
b.x() # E: "str" not callable
[builtins fixtures/property.pyi]

[case testCantImplementAbstractPropertyViaInstanceVariable]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
class B(A):
    def __init__(self) -> None:
        self.x = 1 # E
b = B() # E
b.x.y # E
[builtins fixtures/property.pyi]
[out]
main:7: error: Property "x" defined in "A" is read-only
main:8: error: Cannot instantiate abstract class 'B' with abstract attribute 'x'
main:9: error: "int" has no attribute "y"

[case testSuperWithAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
class B(A):
    @property
    def x(self) -> int:
        return super().x.y # E: "int" has no attribute "y"
[builtins fixtures/property.pyi]
[out]

[case testSuperWithReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
    @x.setter
    def x(self, v: int) -> None: pass
class B(A):
    @property
    def x(self) -> int:
        return super().x.y # E
    @x.setter
    def x(self, v: int) -> None:
        super().x = '' # E
[builtins fixtures/property.pyi]
[out]
main:10: error: "int" has no attribute "y"
main:13: error: Invalid assignment target

[case testOnlyImplementGetterOfReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self) -> int: pass
    @x.setter
    def x(self, v: int) -> None: pass
class B(A):
    @property # E
    def x(self) -> int: pass
b = B()
b.x.y # E
[builtins fixtures/property.pyi]
[out]
main:8: error: Read-only property cannot override read-write property
main:11: error: "int" has no attribute "y"

[case testDynamicallyTypedReadOnlyAbstractProperty]
from abc import abstractproperty, ABCMeta
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self): pass
def f(a: A) -> None:
    a.x.y
    a.x = 1  # E: Property "x" defined in "A" is read-only
[out]

[case testDynamicallyTypedReadOnlyAbstractPropertyForwardRef]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
    a.x.y
    a.x = 1  # E: Property "x" defined in "A" is read-only
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self): pass
[out]

[case testDynamicallyTypedReadWriteAbstractProperty]
from abc import abstractproperty, ABCMeta
def f(a: A) -> None:
    a.x.y
    a.x = 1
class A(metaclass=ABCMeta):
    @abstractproperty
    def x(self): pass
    @x.setter
    def x(self, x): pass
[out]
