1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Improve interface inheritance

This commit is contained in:
Matthew Brown 2016-11-20 11:51:19 -05:00
parent 31aa1c3ce5
commit 4a66dad2ac
4 changed files with 105 additions and 28 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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()) {

View File

@ -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);
}
}