-
-
Notifications
You must be signed in to change notification settings - Fork 947
1.7 incorrectly detects prototypes from PHP extensions #7294
Description
Bug report
Hi,
After I got the upgrade notice of PHPStan 1.7.0 via Dependabot last night, I have started to notice what seems like a regression in 1.7 when it comes to the variance check in PHPStan. It seems that when PHPStan's static reflection attempts to detect the prototype of a method which was declared internally, PHPStan will automatically add null to the signature on PHP 7.4 when an internal method has a default value.
For reference, the setup is:
- PHPStan 1.7.0
- PHP 7.4.29
- Phalcon 4.1.2
Code snippet that reproduces the problem
<?php
declare(strict_types=1);
// If this block is removed and instead the Phalcon extension is loaded, then
// PHPStan will incorrectly detect an error on line 22 because the class
// implementation is not contravariannt with the interface, complaining that
// parameter #1 ($attributes) of 'array' is not contravariant with 'array|null'
namespace Phalcon\Forms\Element {
interface ElementInterface {
/**
* @param array<string, string> $attributes
*/
public function render(array $attributes = []): string;
}
}
namespace Userland\Library\Forms {
use Phalcon\Forms\Element\ElementInterface;
class TextElement implements ElementInterface {
public function render(array $attributes = []): string {
return '<input type="text">';
}
}
}
namespace {
use Userland\Library\Forms\TextElement;
echo (new TextElement())->render();
}Expected output
I expect the code to work as it was prior to 1.7
Other
I dug a little bit into it, and while PHPStan uses BetterReflection, it's detection when it comes to internal methods must be broken somewhere in the chain. Regular Reflection will work, though hessitantly when it comes to default values on 7.4:
F:\>php -r "$r = new \ReflectionClass('\Phalcon\Forms\Element\ElementInterface'); $p = $r->getMethod('render')->getParameters()[0]; var_dump($p->getType()->allowsNull()); var_dump($p->getDefaultValue());"
bool(false)
PHP Fatal error: Uncaught ReflectionException: Cannot determine default value for internal functions in Command line code:1
Stack trace:
#0 Command line code(1): ReflectionParameter->getDefaultValue()
#1 {main}
thrown in Command line code on line 1
Fatal error: Uncaught ReflectionException: Cannot determine default value for internal functions in Command line code:1
Stack trace:
#0 Command line code(1): ReflectionParameter->getDefaultValue()
#1 {main}
thrown in Command line code on line 1$r = new \ReflectionClass('\Phalcon\Forms\Element\ElementInterface');
$p = $r->getMethod('render')->getParameters()[0];
var_dump($p->getType()->allowsNull());
var_dump($p->getDefaultValue());I assume this is where PHPStan or BetterReflection will add null as a suitable default value to the prototype for $attributes combined with the fact that PHP reports $attributes to be optional
The output from PHPStan is something along the lines of this:
------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Line src\UI\Forms\Button.php
------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
36 Parameter #1 $attributes (array) of method Project\UI\Forms\Button::render() is not contravariant with parameter #1 $attributes (array|null) of method Phalcon\Forms\Element\ElementInterface::render().
------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Relevant php --rc output of the above refenced methods:
Method [ <internal:phalcon> abstract public method render ] {
- Parameters [1] {
Parameter #0 [ <optional> array $attributes ]
}
- Return [ string ]
}
... and the same output for PHP 8.0 (where a default value can be obtained via \ReflectionParameter::getDefaultValue() for internal functionality):
Method [ <internal:phalcon> abstract public method render ] {
- Parameters [1] {
Parameter #0 [ <optional> array $attributes = [] ]
}
- Return [ string ]
}
I thought about solving this with stubs, but after some deliberation I am of the impression that this is a bug that should be solved upstream.
Other notes:
- Phalcon's definition of the
render()method (in Zephir which is transpiled to C): https://github.com/phalcon/cphalcon/blob/4.1.x/phalcon/Forms/Element/ElementInterface.zep#L129-L132