1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Merge pull request #10169 from robchett/fix_InheritorViolation

This commit is contained in:
Bruce Weirdan 2023-08-31 23:34:29 +02:00 committed by GitHub
commit c0599975c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 35 deletions

View File

@ -36,6 +36,7 @@ use Psalm\Issue\DuplicateEnumCaseValue;
use Psalm\Issue\ExtensionRequirementViolation; use Psalm\Issue\ExtensionRequirementViolation;
use Psalm\Issue\ImplementationRequirementViolation; use Psalm\Issue\ImplementationRequirementViolation;
use Psalm\Issue\InaccessibleMethod; use Psalm\Issue\InaccessibleMethod;
use Psalm\Issue\InheritorViolation;
use Psalm\Issue\InternalClass; use Psalm\Issue\InternalClass;
use Psalm\Issue\InvalidEnumCaseValue; use Psalm\Issue\InvalidEnumCaseValue;
use Psalm\Issue\InvalidExtendClass; use Psalm\Issue\InvalidExtendClass;
@ -271,6 +272,22 @@ class ClassAnalyzer extends ClassLikeAnalyzer
); );
} }
$class_union = new Union([new TNamedObject($fq_class_name)]);
foreach ($storage->parent_classes + $storage->direct_class_interfaces as $parent_class) {
$parent_storage = $codebase->classlikes->getStorageFor($parent_class);
if ($parent_storage && $parent_storage->inheritors) {
if (!UnionTypeComparator::isContainedBy($codebase, $class_union, $parent_storage->inheritors)) {
IssueBuffer::maybeAdd(
new InheritorViolation(
'Class ' . $fq_class_name . ' is not an allowed inheritor of parent class ' . $parent_class,
new CodeLocation($this, $this->class),
),
$this->getSuppressedIssues(),
);
}
}
}
if ($storage->template_types) { if ($storage->template_types) {
foreach ($storage->template_types as $param_name => $_) { foreach ($storage->template_types as $param_name => $_) {

View File

@ -14,7 +14,6 @@ use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateStandinTypeReplacer; use Psalm\Internal\Type\TemplateStandinTypeReplacer;
use Psalm\Issue\InaccessibleProperty; use Psalm\Issue\InaccessibleProperty;
use Psalm\Issue\InheritorViolation;
use Psalm\Issue\InvalidClass; use Psalm\Issue\InvalidClass;
use Psalm\Issue\InvalidTemplateParam; use Psalm\Issue\InvalidTemplateParam;
use Psalm\Issue\MissingDependency; use Psalm\Issue\MissingDependency;
@ -29,7 +28,6 @@ use Psalm\Plugin\EventHandler\Event\AfterClassLikeExistenceCheckEvent;
use Psalm\StatementsSource; use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\ClassLikeStorage;
use Psalm\Type; use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union; use Psalm\Type\Union;
use UnexpectedValueException; use UnexpectedValueException;
@ -332,23 +330,6 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer
return null; return null;
} }
$classUnion = new Union([new TNamedObject($fq_class_name)]);
foreach ($class_storage->parent_classes + $class_storage->direct_class_interfaces as $parent_class) {
$parent_storage = $codebase->classlikes->getStorageFor($parent_class);
if ($parent_storage && $parent_storage->inheritors) {
if (!UnionTypeComparator::isContainedBy($codebase, $classUnion, $parent_storage->inheritors)) {
IssueBuffer::maybeAdd(
new InheritorViolation(
'Class ' . $fq_class_name . ' is not an allowed inheritor of parent class ' . $parent_class,
$code_location,
),
$suppressed_issues,
);
}
}
}
foreach ($class_storage->invalid_dependencies as $dependency_class_name => $_) { foreach ($class_storage->invalid_dependencies as $dependency_class_name => $_) {
// if the implemented/extended class is stubbed, it may not yet have // if the implemented/extended class is stubbed, it may not yet have
// been hydrated // been hydrated

View File

@ -11,9 +11,13 @@ use Psalm\FileManipulation;
use Psalm\Internal\Analyzer\Statements\Expression\ClassConstAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\ClassConstAnalyzer;
use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\FileManipulation\FileManipulationBuffer;
use Psalm\Internal\Provider\NodeDataProvider; use Psalm\Internal\Provider\NodeDataProvider;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Issue\InheritorViolation;
use Psalm\Issue\ParseError; use Psalm\Issue\ParseError;
use Psalm\Issue\UndefinedInterface; use Psalm\Issue\UndefinedInterface;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Union;
use UnexpectedValueException; use UnexpectedValueException;
use function strtolower; use function strtolower;
@ -109,6 +113,23 @@ class InterfaceAnalyzer extends ClassLikeAnalyzer
} }
} }
$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(); $fq_interface_name = $this->getFQCLN();
if (!$fq_interface_name) { if (!$fq_interface_name) {

View File

@ -857,7 +857,6 @@ class ClassTest extends TestCase
*/ */
class BaseClass {} class BaseClass {}
class FooClass extends BaseClass {} class FooClass extends BaseClass {}
$a = new FooClass();
PHP, PHP,
], ],
'unionInheritorIsAllowed' => [ 'unionInheritorIsAllowed' => [
@ -868,9 +867,7 @@ class ClassTest extends TestCase
*/ */
class BaseClass {} class BaseClass {}
class FooClass extends BaseClass {} class FooClass extends BaseClass {}
$a = new FooClass();
class BarClass extends FooClass {} class BarClass extends FooClass {}
$b = new BarClass();
PHP, PHP,
], ],
'multiInheritorIsAllowed' => [ 'multiInheritorIsAllowed' => [
@ -881,9 +878,7 @@ class ClassTest extends TestCase
*/ */
class BaseClass {} class BaseClass {}
class FooClass extends BaseClass {} class FooClass extends BaseClass {}
$a = new FooClass();
class BarClass extends FooClass {} class BarClass extends FooClass {}
$b = new BarClass();
PHP, PHP,
], ],
'skippedInheritorIsAllowed' => [ 'skippedInheritorIsAllowed' => [
@ -894,9 +889,7 @@ class ClassTest extends TestCase
*/ */
class BaseClass {} class BaseClass {}
class FooClass extends BaseClass {} class FooClass extends BaseClass {}
$a = new FooClass();
class BarClass extends FooClass {} class BarClass extends FooClass {}
$b = new BarClass();
PHP, PHP,
], ],
'CompositeInheritorIsAllowed' => [ 'CompositeInheritorIsAllowed' => [
@ -908,7 +901,6 @@ class ClassTest extends TestCase
class BaseClass {} class BaseClass {}
interface FooInterface {} interface FooInterface {}
class BarClass extends BaseClass implements FooInterface {} class BarClass extends BaseClass implements FooInterface {}
$b = new BarClass();
PHP, PHP,
], ],
'InterfaceInheritorIsAllowed' => [ 'InterfaceInheritorIsAllowed' => [
@ -919,12 +911,10 @@ class ClassTest extends TestCase
*/ */
interface BaseInterface {} interface BaseInterface {}
class FooClass implements BaseInterface {} class FooClass implements BaseInterface {}
$a = new FooClass();
class BarClass implements BaseInterface {} class BarClass implements BaseInterface {}
$b = new BarClass();
PHP, PHP,
], ],
'MultiInterfaceInheritorIsAllowed' => [ 'MultiInterfaceInheritorIsAllowed' => [
'code' => <<<'PHP' 'code' => <<<'PHP'
<?php <?php
/** /**
@ -936,7 +926,16 @@ class ClassTest extends TestCase
*/ */
interface InterfaceB {} interface InterfaceB {}
class FooClass implements InterfaceA, InterfaceB {} class FooClass implements InterfaceA, InterfaceB {}
$a = new FooClass(); PHP,
],
'InterfaceOfInterfaceInheritorIsAllowed' => [
'code' => <<<'PHP'
<?php
/**
* @psalm-inheritors InterfaceB
*/
interface InterfaceA {}
interface InterfaceB extends InterfaceA {}
PHP, PHP,
], ],
]; ];
@ -1393,7 +1392,6 @@ class ClassTest extends TestCase
*/ */
class BaseClass {} class BaseClass {}
class BazClass extends BaseClass {} // this is an error class BazClass extends BaseClass {} // this is an error
$a = new BazClass();
PHP, PHP,
'error_message' => 'InheritorViolation', 'error_message' => 'InheritorViolation',
'ignored_issues' => [], 'ignored_issues' => [],
@ -1406,7 +1404,18 @@ class ClassTest extends TestCase
*/ */
interface BaseInterface {} interface BaseInterface {}
class BazClass implements BaseInterface {} class BazClass implements BaseInterface {}
$a = new BazClass(); PHP,
'error_message' => 'InheritorViolation',
'ignored_issues' => [],
],
'interfaceCannotImplementIfNotInInheritors' => [
'code' => <<<'PHP'
<?php
/**
* @psalm-inheritors FooClass|BarClass
*/
interface BaseInterface {}
interface BazInterface extends BaseInterface {}
PHP, PHP,
'error_message' => 'InheritorViolation', 'error_message' => 'InheritorViolation',
'ignored_issues' => [], 'ignored_issues' => [],
@ -1423,7 +1432,6 @@ class ClassTest extends TestCase
*/ */
interface InterfaceB {} interface InterfaceB {}
class BazClass implements InterFaceA, InterFaceB {} class BazClass implements InterFaceA, InterFaceB {}
$a = new BazClass();
PHP, PHP,
'error_message' => 'InheritorViolation', 'error_message' => 'InheritorViolation',
'ignored_issues' => [], 'ignored_issues' => [],