Bug #73987 [NEW]: Method compatibility check looks to original definition and not parent
| From: | requinix@php.net | Date: | Tue, 24 Jan 2017 14:24:57 +0000 |
| Subject: | Bug #73987 [NEW]: Method compatibility check looks to original definition and not parent | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-206920@lists.php.net to get a copy of this message | ||
From: requinix
Operating system:
PHP version: 7.1.1
Package: Scripting Engine problem
Bug Type: Bug
Bug description:Method compatibility check looks to original definition and not parent
Description:
------------
Originally spotted as bug #73985.
When PHP validates method signatures for compatibility, if a method is
defined in an interface then compatibility is measured against the
interface and any parent method's signature is ignored. This leads to
inconsistencies...
Example #1: method is defined in an interface (valid) -
https://3v4l.org/pblqI
----------
interface I {
public function example($a, $b, $c);
}
class A implements I {
public function example($a, $b = null, $c = null) { } // compatible
with I::example
}
class B extends A {
public function example($a, $b, $c = null) { } // compatible with
I::example
}
Example #2: method is not defined in an interface (invalid) -
https://3v4l.org/bJJ1h
----------
//interface I {
// public function example($a, $b, $c);
//}
class A {
public function example($a, $b = null, $c = null) { }
}
class B extends A {
public function example($a, $b, $c = null) { } // not compatible with
A::example
}
The same signature is used in A and B, however only the second has a
problem. The first is easy to explain on its own ("example" was defined
in I so methods must be compatible with I::example) and the second is
easy to explain on its own ("example" was defined in A so methods must
be compatible with A::example) however the two together are
inconsistent.
The problem appears when calling a function using an I or A parameter
type - https://3v4l.org/OneV3
---
function accepts_i(I $i) {
$i->example(1, 2, 3);
}
accepts_i(new B); // no problem
function accepts_a(A $a) {
$a->example(1);
}
accepts_a(new B); // problem
---
PHP <7.1: missing argument 2 for B::example
PHP >=7.1: ArgumentCountError: Too few arguments to function B::example,
1 passed
A::example() only has one required argument, therefore it should be safe
for accepts_a to call ->example(1). But it isn't.
Like with method parameters, this problem also exists for return types.
Example #3: interface does not have return type (valid) -
https://3v4l.org/Jik7I
----------
interface I {
public function example();
}
class A implements I {
public function example(): int { } // compatible with I::example
}
class B extends A {
public function example(): string { } // compatible with I::example
}
Example #4: class has a return type (invalid) - https://3v4l.org/n1q0G
----------
<?php
//interface I {
// public function example();
//}
class A {
public function example(): int { }
}
class B extends A {
public function example(): string { } // not compatible with
A::example
}
Like with method parameters, this can result in unexpected behavior.
Unlike with method parameters, there's no warning about it -
https://3v4l.org/TUbfJ
---
function accepts_i_expects_any(I $i) {
var_dump($i->example());
}
accepts_i_expects_any(new B); // receives string, no problem
function accepts_a_expects_int(A $a) {
var_dump($a->example());
}
accepts_a_expects_int(new B); // receives string, problem
---
Proposed solution: methods in a subclass are validated against methods
in the nearest ancestor who (re)defines the method - be that normally,
as abstract, or using a trait. The result is that since A implemented
example(), B::example gets validated against that rather than the
original definition of I::example.
BC: Yes, but code reliant on current behavior is susceptible to the
"inconsistencies" noted earlier so it's already flawed.
Test script:
---------------
<?php // https://3v4l.org/gl1Gt
interface I {
public function example($a, $b, $c);
}
class A implements I {
public function example($a, $b = null, $c = null): int { }
}
class B extends A {
public function example($a, $b, $c = null): string { }
}
?>
Expected result:
----------------
Error that B::example is not compatible with A::example, due to the
return type (a fatal error by itself) and the second required argument
(a warning by itself).
Actual result:
--------------
No error(s).
--
Edit bug report at https://bugs.php.net/bug.php?id=73987&edit=1
--
Try a snapshot (PHP 5.4): https://bugs.php.net/fix.php?id=73987&r=trysnapshot54
Try a snapshot (PHP 5.5): https://bugs.php.net/fix.php?id=73987&r=trysnapshot55
Try a snapshot (trunk): https://bugs.php.net/fix.php?id=73987&r=trysnapshottrunk
Fixed in SVN: https://bugs.php.net/fix.php?id=73987&r=fixed
Fixed in release: https://bugs.php.net/fix.php?id=73987&r=alreadyfixed
Need backtrace: https://bugs.php.net/fix.php?id=73987&r=needtrace
Need Reproduce Script: https://bugs.php.net/fix.php?id=73987&r=needscript
Try newer version: https://bugs.php.net/fix.php?id=73987&r=oldversion
Not developer issue: https://bugs.php.net/fix.php?id=73987&r=support
Expected behavior: https://bugs.php.net/fix.php?id=73987&r=notwrong
Not enough info: https://bugs.php.net/fix.php?id=73987&r=notenoughinfo
Submitted twice: https://bugs.php.net/fix.php?id=73987&r=submittedtwice
register_globals: https://bugs.php.net/fix.php?id=73987&r=globals
PHP 4 support discontinued: https://bugs.php.net/fix.php?id=73987&r=php4
Daylight Savings: https://bugs.php.net/fix.php?id=73987&r=dst
IIS Stability: https://bugs.php.net/fix.php?id=73987&r=isapi
Install GNU Sed: https://bugs.php.net/fix.php?id=73987&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=73987&r=float
No Zend Extensions: https://bugs.php.net/fix.php?id=73987&r=nozend
MySQL Configuration Error: https://bugs.php.net/fix.php?id=73987&r=mysqlcfg