mirror of
https://github.com/danog/psalm.git
synced 2025-01-19 03:54:42 +01:00
Add less severe issue for docblock property type invariance cc @bdsl
This is less likely to break everything
This commit is contained in:
parent
04bb2b1182
commit
f2d202e2bb
@ -313,7 +313,8 @@
|
||||
<xs:element name="NoInterfaceProperties" type="ClassIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NonStaticSelfCall" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NoValue" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NonInvariantPropertyType" type="MethodIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NonInvariantDocblockPropertyType" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NonInvariantPropertyType" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NullableReturnStatement" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NullArgument" type="ArgumentIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="NullArrayAccess" type="IssueHandlerType" minOccurs="0" />
|
||||
|
@ -0,0 +1,25 @@
|
||||
# NonInvariantDocblockPropertyType
|
||||
|
||||
Emitted when a public or protected class property has a different docblock type to the matching property in the parent class.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class A {
|
||||
/** @var string */
|
||||
public $foo = 'hello';
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
/** @var null|string */
|
||||
public $foo;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Why this is bad
|
||||
|
||||
For non-typed properties, it can cause a type system violation when code written against the parent class reads or writes a value on an object of the child class.
|
||||
|
||||
If the child class widens the type then reading the value may return unexpected value that client code cannot deal with. If the child class narrows the type then code setting the value may set
|
||||
it to an invalid value.
|
@ -6,21 +6,15 @@ Emitted when a public or protected class property has a different type to the ma
|
||||
<?php
|
||||
|
||||
class A {
|
||||
/** @var string */
|
||||
public $foo = 'hello';
|
||||
public string $foo = 'hello';
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
/** @var null|string */
|
||||
public $foo;
|
||||
public ?string $foo;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Why this is bad
|
||||
|
||||
For typed properties, this can cause a compile error. For non-typed
|
||||
properties, it can cause a type system violation when code written against the parent class reads or writes a value on an object of the child class.
|
||||
|
||||
If the child class widens the type then reading the value may return unexpected value that client code cannot deal with. If the child class narrows the type then code setting the value may set
|
||||
it to an invalid value.
|
||||
For typed properties, this can cause a compile error.
|
||||
|
@ -31,6 +31,7 @@ use Psalm\Issue\MissingImmutableAnnotation;
|
||||
use Psalm\Issue\MissingPropertyType;
|
||||
use Psalm\Issue\MissingTemplateParam;
|
||||
use Psalm\Issue\MutableDependency;
|
||||
use Psalm\Issue\NonInvariantDocblockPropertyType;
|
||||
use Psalm\Issue\NonInvariantPropertyType;
|
||||
use Psalm\Issue\OverriddenPropertyAccess;
|
||||
use Psalm\Issue\PropertyNotSetInConstructor;
|
||||
@ -663,20 +664,43 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($property_storage->type && $guide_property_storage->type && $property_storage->location &&
|
||||
!$property_storage->type->equals($guide_property_storage->type)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new NonInvariantPropertyType(
|
||||
'Property ' . $fq_class_name . '::$' . $property_name
|
||||
. ' has type ' . $property_storage->type->getId()
|
||||
. ", not invariant with " . $guide_class_name . '::$' . $property_name . ' of type ' .
|
||||
$guide_property_storage->type->getId(),
|
||||
$property_storage->location
|
||||
),
|
||||
$property_storage->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
if ($property_storage->type
|
||||
&& $guide_property_storage->type
|
||||
&& $property_storage->location
|
||||
&& !$property_storage->type->equals($guide_property_storage->type)
|
||||
) {
|
||||
if ($property_storage->type->from_docblock
|
||||
&& $guide_property_storage->type->from_docblock
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new NonInvariantDocblockPropertyType(
|
||||
'Property ' . $fq_class_name . '::$' . $property_name
|
||||
. ' has type ' . $property_storage->type->getId()
|
||||
. ", not invariant with " . $guide_class_name . '::$'
|
||||
. $property_name . ' of type '
|
||||
. $guide_property_storage->type->getId(),
|
||||
$property_storage->location
|
||||
),
|
||||
$property_storage->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new NonInvariantPropertyType(
|
||||
'Property ' . $fq_class_name . '::$' . $property_name
|
||||
. ' has type ' . $property_storage->type->getId()
|
||||
. ", not invariant with " . $guide_class_name . '::$'
|
||||
. $property_name . ' of type '
|
||||
. $guide_property_storage->type->getId(),
|
||||
$property_storage->location
|
||||
),
|
||||
$property_storage->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer
|
||||
protected $class;
|
||||
|
||||
/**
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
* @var StatementsSource
|
||||
*/
|
||||
protected $source;
|
||||
|
@ -24,7 +24,7 @@ use function preg_match;
|
||||
class ClosureAnalyzer extends FunctionLikeAnalyzer
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
* @var PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\ArrowFunction
|
||||
*/
|
||||
protected $function;
|
||||
|
@ -13,7 +13,7 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
{
|
||||
/**
|
||||
* @var PhpParser\Node\Stmt\Function_
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
protected $function;
|
||||
|
||||
|
@ -71,7 +71,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
||||
|
||||
/**
|
||||
* @var StatementsSource
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
|
@ -22,7 +22,7 @@ use function in_array;
|
||||
class MethodAnalyzer extends FunctionLikeAnalyzer
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
* @var PhpParser\Node\Stmt\ClassMethod
|
||||
*/
|
||||
protected $function;
|
||||
@ -166,7 +166,7 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static function isMethodVisible(
|
||||
\Psalm\Internal\MethodIdentifier $method_id,
|
||||
Context $context,
|
||||
|
@ -20,7 +20,7 @@ class NamespaceAnalyzer extends SourceAnalyzer
|
||||
|
||||
/**
|
||||
* @var FileAnalyzer
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
|
@ -97,7 +97,7 @@ class PropertyTypeProvider
|
||||
): ?Type\Union {
|
||||
|
||||
if ($source) {
|
||||
$source->addSuppressedIssues(['NonInvariantPropertyType']);
|
||||
$source->addSuppressedIssues(['NonInvariantDocblockPropertyType']);
|
||||
}
|
||||
|
||||
foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) {
|
||||
|
11
src/Psalm/Issue/NonInvariantDocblockPropertyType.php
Normal file
11
src/Psalm/Issue/NonInvariantDocblockPropertyType.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psalm\Issue;
|
||||
|
||||
final class NonInvariantDocblockPropertyType extends CodeIssue
|
||||
{
|
||||
public const ERROR_LEVEL = 3;
|
||||
public const SHORTCODE = 267;
|
||||
}
|
@ -14,7 +14,7 @@ class TArray extends \Psalm\Type\Atomic
|
||||
|
||||
/**
|
||||
* @var array{\Psalm\Type\Union, \Psalm\Type\Union}
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
public $type_params;
|
||||
|
||||
|
@ -40,7 +40,7 @@ class PluginTest extends \Psalm\Tests\TestCase
|
||||
|
||||
/**
|
||||
* @var ?\Psalm\Internal\Analyzer\ProjectAnalyzer
|
||||
* @psalm-suppress NonInvariantPropertyType
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
protected $project_analyzer;
|
||||
|
||||
|
@ -50,38 +50,34 @@ class PropertyTypeInvariance extends TestCase
|
||||
public function providerInvalidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'variantDocblockProperties' => [
|
||||
'<?php
|
||||
class ParentClass
|
||||
{
|
||||
/** @var null|string */
|
||||
protected $mightExist;
|
||||
}
|
||||
|
||||
class ChildClass extends ParentClass
|
||||
{
|
||||
/** @var string */
|
||||
protected $mightExist = "";
|
||||
}',
|
||||
'error_message' => 'NonInvariantDocblockPropertyType',
|
||||
],
|
||||
'variantProperties' => [
|
||||
'
|
||||
<?php
|
||||
'<?php
|
||||
class ParentClass
|
||||
{
|
||||
protected ?string $mightExist = null;
|
||||
}
|
||||
|
||||
class ParentClass
|
||||
{
|
||||
/** @var null|string */
|
||||
protected $mightExist;
|
||||
|
||||
protected ?string $mightExistNative = null;
|
||||
|
||||
/** @var string */
|
||||
protected $doesExist = "";
|
||||
|
||||
protected string $doesExistNative = "";
|
||||
}
|
||||
|
||||
class ChildClass extends ParentClass
|
||||
{
|
||||
/** @var string */
|
||||
protected $mightExist = "";
|
||||
|
||||
protected string $mightExistNative = "";
|
||||
|
||||
/** @var null|string */
|
||||
protected $doesExist = "";
|
||||
|
||||
protected ?string $doesExistNative = "";
|
||||
}
|
||||
',
|
||||
class ChildClass extends ParentClass
|
||||
{
|
||||
protected string $mightExist = "";
|
||||
}',
|
||||
'error_message' => 'NonInvariantPropertyType',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user