Bug #78818 [Com]: Current Type variance changes breaks the Liskov substitution principle
[email protected] ("me at paveljanda dot com") Fri, 15 Nov 2019 13:40:31 +0000
| Newsgroups | php.standards |
|---|---|
| Message-ID | <[email protected]> |
Edit report at https://bugs.php.net/bug.php?id=78818&edit=1
ID: 78818
Comment by: me at paveljanda dot com
Reported by: me at paveljanda dot com
Summary: Current Type variance changes breaks the Liskov
substitution principle
Status: Not a bug
Type: Bug
Package: PHP Language Specification
Operating System: Linux
PHP Version: 7.4.0RC6
Block user comment: N
Private report: N
New Comment:
Thanks for and answer.
I totally agree with you that this code's abstraction is wrong. But it should be for this demonstration.
What about the number 2? How is it that the language is behaving different way event both arguments are of the same type "FuelType"?
Previous Comments:
------------------------------------------------------------------------
[2019-11-15 13:13:56] [email protected]
I'm sorry but this is the correct behaviour as per LSP.
Your issue here is that your abstraction (interface/classes) is wrong and bending the rules of correct type safety just to make a wrong abstraction work is not correct.
It seems you are confusing co and contravariance.
Covariance is only safe in return types and contravariuance is only safe in argument types.
Moreover you are not doing any of that you are doing flat out variance which is inherently incompatible with LSP.
------------------------------------------------------------------------
[2019-11-15 13:03:35] me at paveljanda dot com
Description:
------------
1, Liskov substitution principle:
interface FuelType
{
}
class Gas implements FuelType
{
public function burn(int $litres) {}
};
class Battery implements FuelType
{
public function discharge(int $kWh) {}
};
class Vehicle
{
public function drive(Gas $fuel)
{
$battery->burn(10);
}
}
class ElectricVehicle extends Vehicle
{
public function drive(FuelType $battery)
{
// do something
}
}
(new ElectricVehicle)->drive(new Battery);
-> This code works fine in 7.4 RC6. I don't think it should. That code would enable ElectricVehicle::drive() to accept "smaller" argument with less functionality that the "bigger" Gas class that is required by parent (!!) class method.
2. In the example above, when I change the ElectricVehicle into following form:
class ElectricVehicle extends Vehicle
{
public function drive(Battery $battery)
{
$battery->discharge(10);
}
}
, PHP tells me different result (Warning: Declaration of ElectricVehicle::drive(Battery $battery) should be compatible with Vehicle::drive(Gas $fuel)) event though Battery is a type of FuelType which worked fine in the previous example.
It makes perfect sense to use Type variance in the opposite direction - classes that extend from Vehicle should be able to accept a class that extends Gas type. It should be OK to extends the successor behaviour. But it's not OK do make it both directions.
Thank you all for spending some time with that report!
Pavel Janda
Test script:
---------------
https://gist.github.com/paveljanda/cba6d31be920217c620289e54fed7c1c
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=78818&edit=1