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:
commit
c0599975c6
@ -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 => $_) {
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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' => [],
|
||||||
|
Loading…
Reference in New Issue
Block a user