From 0bab656a7fbfc88261cdb9dd57d9bf301fff7a2d Mon Sep 17 00:00:00 2001 From: robchett Date: Thu, 31 Aug 2023 19:12:58 +0100 Subject: [PATCH] Apply psalm-inheritors to interfaces too --- .../Internal/Analyzer/InterfaceAnalyzer.php | 20 ++++++++++ tests/ClassTest.php | 37 ++++++++++++------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php b/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php index dd2a107498b..3946eb1ad9f 100644 --- a/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php @@ -11,9 +11,13 @@ use Psalm\Internal\Analyzer\Statements\Expression\ClassConstAnalyzer; use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\Provider\NodeDataProvider; +use Psalm\Internal\Type\Comparator\UnionTypeComparator; +use Psalm\Issue\InheritorViolation; use Psalm\Issue\ParseError; use Psalm\Issue\UndefinedInterface; use Psalm\IssueBuffer; +use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Union; use UnexpectedValueException; use function strtolower; @@ -109,6 +113,22 @@ public function analyze(): void } } + $class_union = new Union([new TNamedObject($fq_interface_name)]); + foreach ($class_storage->direct_interface_parents as $parent_interface) { + $parent_storage = $codebase->classlikes->getStorageFor($parent_interface); + if ($parent_storage && $parent_storage->inheritors) { + if (!UnionTypeComparator::isContainedBy($codebase, $class_union, $parent_storage->inheritors)) { + IssueBuffer::maybeAdd( + new InheritorViolation( + 'Interface ' . $fq_interface_name . ' is not an allowed inheritor of parent interface ' . $parent_interface, + new CodeLocation($this, $this->class), + ), + $this->getSuppressedIssues(), + ); + } + } + } + $fq_interface_name = $this->getFQCLN(); if (!$fq_interface_name) { diff --git a/tests/ClassTest.php b/tests/ClassTest.php index e1e85b10692..e134f6d2682 100644 --- a/tests/ClassTest.php +++ b/tests/ClassTest.php @@ -857,7 +857,6 @@ private final function __construct() {} */ class BaseClass {} class FooClass extends BaseClass {} - $a = new FooClass(); PHP, ], 'unionInheritorIsAllowed' => [ @@ -868,9 +867,7 @@ class FooClass extends BaseClass {} */ class BaseClass {} class FooClass extends BaseClass {} - $a = new FooClass(); class BarClass extends FooClass {} - $b = new BarClass(); PHP, ], 'multiInheritorIsAllowed' => [ @@ -881,9 +878,7 @@ class BarClass extends FooClass {} */ class BaseClass {} class FooClass extends BaseClass {} - $a = new FooClass(); class BarClass extends FooClass {} - $b = new BarClass(); PHP, ], 'skippedInheritorIsAllowed' => [ @@ -894,9 +889,7 @@ class BarClass extends FooClass {} */ class BaseClass {} class FooClass extends BaseClass {} - $a = new FooClass(); class BarClass extends FooClass {} - $b = new BarClass(); PHP, ], 'CompositeInheritorIsAllowed' => [ @@ -908,7 +901,6 @@ class BarClass extends FooClass {} class BaseClass {} interface FooInterface {} class BarClass extends BaseClass implements FooInterface {} - $b = new BarClass(); PHP, ], 'InterfaceInheritorIsAllowed' => [ @@ -919,12 +911,10 @@ class BarClass extends BaseClass implements FooInterface {} */ interface BaseInterface {} class FooClass implements BaseInterface {} - $a = new FooClass(); class BarClass implements BaseInterface {} - $b = new BarClass(); PHP, - ], - 'MultiInterfaceInheritorIsAllowed' => [ + ], + 'MultiInterfaceInheritorIsAllowed' => [ 'code' => <<<'PHP' [ + 'code' => <<<'PHP' + 'InheritorViolation', 'ignored_issues' => [], ], + 'interfaceCannotImplementIfNotInInheritors' => [ + 'code' => <<<'PHP' + 'InheritorViolation', + 'ignored_issues' => [], + ], 'UnfulfilledInterfaceInheritors' => [ 'code' => <<<'PHP'