How to Redefine a Type Hint to a Descendant Class When Extending an Abstract Class

Is there a way to redefine a type hint to a descendant class when extending an abstract class?

I wouldn't expect so, as it can break type hinting contracts. Suppose a function foo took an AbstractFactory and was passed a SimpleFactory.

function foo(AbstractFactory $maker) {
$attr = new Attribute();
$maker->update($attr, 42);
}
...
$packager=new SimpleFactory();
foo($packager);

foo calls update and passes an Attribute to the factory, which it should take because the AbstractFactory::update method signature promises it can take an Attribute. Bam! The SimpleFactory has an object of type it can't handle properly.

class Attribute {}
class SimpleAttribute extends Attribute {
public function spin() {...}
}
class SimpleFactory extends AbstractFactory {
public function update(SimpleAttribute $attr, $data) {
$attr->spin(); // This will fail when called from foo()
}
}

In contract terminology, descendent classes must honor the contracts of their ancestors, which means function parameters can get more basal/less specified/offer a weaker contract and return values can be more derived/more specified/offer a stronger contract. The principle is described for Eiffel (arguably the most popular design-by-contract language) in "An Eiffel Tutorial: Inheritance and Contracts". Weakening and strengthening of types are examples of contravariance and covariance, respectively.

In more theoretical terms, this is an example of LSP violation. No, not that LSP; the Liskov Substitution Principle, which states that objects of a subtype can be substituted for objects of a supertype. SimpleFactory is a subtype of AbstractFactory, and foo takes an AbstractFactory. Thus, according to LSP, foo should take a SimpleFactory. Doing so causes a "Call to undefined method" fatal error, which means LSP has been violated.

Typesafe / Type hinting in child classes of an abstract class in PHP

Having done a bit more digging around, and thanks to a helpful post from deceze, I think this idea hasn't been implemented. He has pointed out a similar answer it to which breaks the rules of SOLID.

http://en.wikipedia.org/wiki/SOLID

Basically - This says that I cannot change the parent class: this is the point of "extend".
I can only extend, not modify the parent class.
Changing the typesafeing is modifying the class.

HOWEVER - I would argue that this example doesn't break the rules of SOLID.
Typesafing with class that is extended from the base class should be allowed: You can never have a "User" that is not a "BaseModel"... so it is not modifying the parent class.

It looks like the only way to truly do this is

if ($item instanceof User) 
{
// logic
}

In my case the parent class is probably enough to catch most issues, but it's a shame I can't typesafe it the way I intended.

[Thanks to deceze. Upvoted.]

Type hint for descendant classes

If I am not mistaken you get this error:

Declaration of PimpleConfigurer_Factories::configure() must be compatible with Pimple_Config::configure(Pimple $container) ...

Which means: If you define a method in a super class or in an interface, all sub classes (or classes implementing the interface) must use exactly this definition. You cannot use another type here.

As for your quote from the documentation:

If class or interface is specified as type hint then all its children or implementations are allowed too.

This only means that you can pass a variable which is of a certain type or all its children.

For example: Say you have the following classes:

class Car {
protected $hp = 50;
public function getHp() { return $this->hp; }
}

class SuperCar extends Car {
protected $hp = 700;
}

And a function (or method, no difference there) with type hint:

function showHorsePower(Car $car) {
echo $car->getHp();
}

You can now pass all objects of type Car and all its sub classes (here SuperCar) to this function, like:

showHorsePower(new Car());
showHorsePower(new SuperCar());

Tell methods of base class to use more restricted type hints in a derived class?

In more static languages you would create Store as a generic class and use Car as a type parameter when inheriting from Store.

We can actually do that using the typing module in python.

Here is a minimal example:

from typing import Generic, TypeVar

T = TypeVar('T') # this is the generic placeholder for a type

# Store is a generic class with type parameter T
class Store(Generic[T]):
def get(self) -> T: # this returns a T
return self.load_object()

def load_object(self) -> T: # this also returns a T
raise NotImplementedError

class Car:
def __init__(self, color):
self.color = color

# Now we inherit from the Store and use Car as the type parameter
class CarStore(Store[Car]):
def load_object(self):
return Car('red')

s = CarStore()
c = s.get()
print(c.color) # Code completion works and no warnings are shown

Edit:

To address ShadowRanger's note: If you want Car and all the products to have a common base class you can use the bound parameter of the TypeVar. Thank you juanpa.arrivillaga for the hint.

So we create a Product class and bind the TypeVar to it.

class Product:
def get_id(self):
raise NotImplementedError

T = TypeVar('T', bound=Product)

Mypy will now complain about this:

class CarStore(Store[Car]):
def load_object(self):
return Car('red')

because a Car is not a Product. So let' change that, too:

class Car(Product):
def get_id(self):
return ...

def __init__(self, color):
self.color = color

And now, mypy is happy.

Edit2:

Here is the full code with some more annotations, that make even mypy --strict happy.

from typing import Generic, TypeVar

class Product:
def get_id(self) -> int:
raise NotImplementedError

T = TypeVar('T', bound=Product)

class Store(Generic[T]):
def get(self) -> T:
return self.load_object()

def load_object(self) -> T:
raise NotImplementedError

class Car(Product):
def get_id(self) -> int:
return hash(self.color)

def __init__(self, color: str):
self.color = color

class CarStore(Store[Car]):
def load_object(self) -> Car:
return Car('red')

if __name__ == '__main__':
s = CarStore()
c = s.get()
print(c.color)

Php type hinting not getting along with interfaces and abstract classes?

php doesn't seem to be recognizing the signatures of AnAbstractClass::method and ConcreteClass::method as compatible.

PHP is correct, they're not compatible. By allowing only instances of AClass (or its children) to be passed to ConcreteClass::method, you're breaking the contract that AnAbstractClass provides: Any of its subclasses must accept AnInterface as an argument to its method().

If your example worked, and I had another class BClass implementing AnInterface, we'd have a situation where according to AnAbstractClass, method() should accept instances of BClass, while according to ConcreteClass, it shouldn't.

Change your signature for ConcreteClass::method to match that of AnAbstractClass::method.

Subclass in type hinting

When you specify cls: A, you're saying that cls expects an instance of type A. The type hint to specify cls as a class object for the type A (or its subtypes) uses typing.Type.

from typing import Type
def process_any_subclass_type_of_A(cls: Type[A]):
pass

From The type of class objects
:

Sometimes you want to talk about class objects that inherit from a
given class. This can be spelled as Type[C] where C is a class. In
other words, when C is the name of a class, using C to annotate an
argument declares that the argument is an instance of C (or of a
subclass of C), but using Type[C] as an argument annotation declares
that the argument is a class object deriving from C (or C itself).

Python: how to override type hint on an instance attribute in a subclass?

You can give a type hint on my_class attribute in the beginning of class definition:

class SubclassOfFoo(Foo):
my_class: SubclassOfSomething # <- here

def get_something(self) -> SubclassOfSomething:
return SubclassOfSomething()

def do_something_special(self):
self.my_class.something_special()

After that there is no warning Unresolved attribute reference 'something_special' for class 'Something' from PyCharm inspection because now my_class is known to be SubclassOfSomething not Something.

Hinting abstract classes in TypeScript

We want to declare a value which, when called with new, returns an Animal. This is written as:

new() => Animal

Note the similarity to function types (() => Animal).

The full code is then:

const all: Array<new() => Animal> = [Cat, Dog];

Or, using an interface for clarity:

interface AnimalConstructor {
new(): Animal;
}

const all: AnimalConstructor[] = [Cat, Dog];


Related Topics



Leave a reply



Submit