mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Improve interface inheritance
This commit is contained in:
parent
31aa1c3ce5
commit
4a66dad2ac
@ -179,6 +179,13 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
*/
|
||||
protected static $class_implements = [];
|
||||
|
||||
/**
|
||||
* A lookup table for interface parents
|
||||
*
|
||||
* @var array<string, array<string>>
|
||||
*/
|
||||
protected static $parent_interfaces = [];
|
||||
|
||||
/**
|
||||
* @var array<string,string>
|
||||
*/
|
||||
@ -277,10 +284,20 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
if ($this->parent_class && $this->registerParentClassProperties($this->parent_class) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof InterfaceChecker || $this instanceof ClassChecker) {
|
||||
$extra_interfaces = [];
|
||||
|
||||
foreach (self::$class_implements[$this->fq_class_name] as $interface_id => $interface_name) {
|
||||
if ($this instanceof InterfaceChecker) {
|
||||
$parent_interfaces = InterfaceChecker::getParentInterfaces($this->fq_class_name);
|
||||
$extra_interfaces = $parent_interfaces;
|
||||
}
|
||||
else {
|
||||
$parent_interfaces = self::$class_implements[$this->fq_class_name];
|
||||
}
|
||||
|
||||
foreach ($parent_interfaces as $interface_name) {
|
||||
if (self::checkFullyQualifiedClassLikeName(
|
||||
$interface_name,
|
||||
$this->file_name,
|
||||
@ -303,8 +320,13 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
foreach ($extra_interfaces as $extra_interface_name) {
|
||||
FileChecker::addFileInheritanceToClass($long_file_name, $extra_interface_name);
|
||||
|
||||
self::$class_implements[$this->fq_class_name][strtolower($extra_interface_name)] =
|
||||
$extra_interface_name;
|
||||
if ($this instanceof ClassChecker) {
|
||||
self::$class_implements[$this->fq_class_name][strtolower($extra_interface_name)] =
|
||||
$extra_interface_name;
|
||||
}
|
||||
else {
|
||||
$this->registerInheritedMethods($extra_interface_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1072,6 +1094,10 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
|
||||
$class_constants = $reflected_class->getConstants();
|
||||
|
||||
if ($reflected_class->isInterface()) {
|
||||
self::$parent_interfaces[$class_name] = [];
|
||||
}
|
||||
|
||||
self::$public_class_constants[$class_name] = [];
|
||||
|
||||
foreach ($class_constants as $name => $value) {
|
||||
@ -1119,7 +1145,6 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
|
||||
self::$class_methods[$class_name] = [];
|
||||
|
||||
/** @var \ReflectionMethod $reflection_method */
|
||||
foreach ($reflection_methods as $reflection_method) {
|
||||
MethodChecker::extractReflectionMethodInfo($reflection_method);
|
||||
|
||||
|
@ -6,11 +6,6 @@ use Psalm\StatementsSource;
|
||||
|
||||
class InterfaceChecker extends ClassLikeChecker
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<string>>
|
||||
*/
|
||||
protected static $parent_interfaces = [];
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
@ -37,9 +32,9 @@ class InterfaceChecker extends ClassLikeChecker
|
||||
self::$existing_interfaces[$interface_name] = true;
|
||||
self::$existing_interfaces_ci[strtolower($interface_name)] = true;
|
||||
|
||||
if ($interface->extends) {
|
||||
self::$parent_interfaces[$interface_name] = [];
|
||||
self::$parent_interfaces[$interface_name] = [];
|
||||
|
||||
if ($interface->extends) {
|
||||
foreach ($interface->extends as $extended_interface) {
|
||||
$extended_interface_name = self::getFQCLNFromNameObject(
|
||||
$extended_interface,
|
||||
@ -113,10 +108,6 @@ class InterfaceChecker extends ClassLikeChecker
|
||||
|
||||
$extended_interfaces = [];
|
||||
|
||||
if (!isset(self::$parent_interfaces[$interface_name])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach (self::$parent_interfaces[$interface_name] as $extended_interface_name) {
|
||||
$extended_interfaces[] = $extended_interface_name;
|
||||
|
||||
|
@ -152,7 +152,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
$overridden_method_ids = self::getOverriddenMethodIds($method_id);
|
||||
|
||||
foreach ($overridden_method_ids as $overridden_method_id) {
|
||||
if (self::$method_return_types[$overridden_method_id]) {
|
||||
if (isset(self::$method_return_types[$overridden_method_id])) {
|
||||
$implementary_return_type = self::$method_return_types[$overridden_method_id];
|
||||
|
||||
if ($implementary_return_type->isNull()) {
|
||||
|
@ -25,7 +25,7 @@ class InterfaceTest extends PHPUnit_Framework_TestCase
|
||||
FileChecker::clearCache();
|
||||
}
|
||||
|
||||
public function testExtends()
|
||||
public function testExtendsAndImplements()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
interface A
|
||||
@ -38,9 +38,7 @@ class InterfaceTest extends PHPUnit_Framework_TestCase
|
||||
|
||||
interface B
|
||||
{
|
||||
public function bar() {
|
||||
|
||||
}
|
||||
public function bar();
|
||||
}
|
||||
|
||||
interface C extends A, B
|
||||
@ -48,9 +46,7 @@ class InterfaceTest extends PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function baz() {
|
||||
|
||||
}
|
||||
public function baz();
|
||||
}
|
||||
|
||||
class D implements C
|
||||
@ -68,13 +64,8 @@ class InterfaceTest extends PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
function qux(A $a) {
|
||||
|
||||
}
|
||||
|
||||
$cee = (new D())->baz();
|
||||
$dee = (new D())->foo();
|
||||
qux(new D());
|
||||
?>
|
||||
');
|
||||
|
||||
@ -84,4 +75,74 @@ class InterfaceTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('string', (string) $context->vars_in_scope['$cee']);
|
||||
$this->assertEquals('string', (string) $context->vars_in_scope['$dee']);
|
||||
}
|
||||
|
||||
public function testIsExtendedInterface()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
interface A
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function foo();
|
||||
}
|
||||
|
||||
interface B extends A
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function baz();
|
||||
}
|
||||
|
||||
class C implements B
|
||||
{
|
||||
public function foo()
|
||||
{
|
||||
}
|
||||
|
||||
public function baz()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
function qux(A $a) {
|
||||
}
|
||||
|
||||
qux(new C());
|
||||
?>
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $stmts);
|
||||
$context = new Context('somefile.php');
|
||||
$file_checker->check(true, true, $context);
|
||||
}
|
||||
|
||||
public function testExtendsWithMethod()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
interface A
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function foo();
|
||||
}
|
||||
|
||||
interface B extends A
|
||||
{
|
||||
public function bar();
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
function mux(B $b) {
|
||||
$b->foo();
|
||||
}
|
||||
?>
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $stmts);
|
||||
$context = new Context('somefile.php');
|
||||
$file_checker->check(true, true, $context);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user