1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Forbid implementing some interfaces

- `Throwable` can only be implemented when classes extend one of
  `Exception` or `Error`
- `UnitEnum` and `BackedEnum` cannot be implemented by user-defined
  classes

Refs vimeo/psalm#7722
This commit is contained in:
Bruce Weirdan 2023-02-12 02:42:59 -04:00
parent 0fb0714141
commit 085e8f6fb2
No known key found for this signature in database
GPG Key ID: CFC3AAB181751B0D
9 changed files with 106 additions and 0 deletions

View File

@ -272,6 +272,7 @@
<xs:element name="InvalidFalsableReturnType" type="IssueHandlerType" minOccurs="0" /> <xs:element name="InvalidFalsableReturnType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidFunctionCall" type="IssueHandlerType" minOccurs="0" /> <xs:element name="InvalidFunctionCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidGlobal" type="IssueHandlerType" minOccurs="0" /> <xs:element name="InvalidGlobal" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidInterfaceImplementation" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidIterator" type="IssueHandlerType" minOccurs="0" /> <xs:element name="InvalidIterator" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidLiteralArgument" type="ArgumentIssueHandlerType" minOccurs="0" /> <xs:element name="InvalidLiteralArgument" type="ArgumentIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidMethodCall" type="IssueHandlerType" minOccurs="0" /> <xs:element name="InvalidMethodCall" type="IssueHandlerType" minOccurs="0" />

View File

@ -48,6 +48,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [InvalidEnumMethod](issues/InvalidEnumMethod.md) - [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md) - [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidGlobal](issues/InvalidGlobal.md) - [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidInterfaceImplementation](issues/InvalidInterfaceImplementation.md)
- [InvalidParamDefault](issues/InvalidParamDefault.md) - [InvalidParamDefault](issues/InvalidParamDefault.md)
- [InvalidParent](issues/InvalidParent.md) - [InvalidParent](issues/InvalidParent.md)
- [InvalidPassByReference](issues/InvalidPassByReference.md) - [InvalidPassByReference](issues/InvalidPassByReference.md)

View File

@ -75,6 +75,7 @@
- [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md) - [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md)
- [InvalidFunctionCall](issues/InvalidFunctionCall.md) - [InvalidFunctionCall](issues/InvalidFunctionCall.md)
- [InvalidGlobal](issues/InvalidGlobal.md) - [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidInterfaceImplementation](issues/InvalidInterfaceImplementation.md)
- [InvalidIterator](issues/InvalidIterator.md) - [InvalidIterator](issues/InvalidIterator.md)
- [InvalidLiteralArgument](issues/InvalidLiteralArgument.md) - [InvalidLiteralArgument](issues/InvalidLiteralArgument.md)
- [InvalidMethodCall](issues/InvalidMethodCall.md) - [InvalidMethodCall](issues/InvalidMethodCall.md)

View File

@ -0,0 +1,15 @@
# InvalidInterfaceImplementation
Emitted when trying to implement interface that cannot be implemented (e.g. `Throwable`, `UnitEnum`, `BackedEnum`).
```php
<?php
class E implements UnitEnum
{
public static function cases(): array
{
return [];
}
}
```

View File

@ -39,6 +39,7 @@ use Psalm\Issue\InaccessibleMethod;
use Psalm\Issue\InternalClass; use Psalm\Issue\InternalClass;
use Psalm\Issue\InvalidEnumCaseValue; use Psalm\Issue\InvalidEnumCaseValue;
use Psalm\Issue\InvalidExtendClass; use Psalm\Issue\InvalidExtendClass;
use Psalm\Issue\InvalidInterfaceImplementation;
use Psalm\Issue\InvalidTraversableImplementation; use Psalm\Issue\InvalidTraversableImplementation;
use Psalm\Issue\MethodSignatureMismatch; use Psalm\Issue\MethodSignatureMismatch;
use Psalm\Issue\MismatchingDocblockPropertyType; use Psalm\Issue\MismatchingDocblockPropertyType;
@ -2114,6 +2115,35 @@ class ClassAnalyzer extends ClassLikeAnalyzer
); );
} }
if ($fq_interface_name_lc === 'throwable'
&& $codebase->analysis_php_version_id >= 7_00_00
&& !$storage->abstract
&& !isset($storage->parent_classes['exception'])
&& !isset($storage->parent_classes['error'])
) {
IssueBuffer::maybeAdd(
new InvalidInterfaceImplementation(
'Classes implementing Throwable should extend Exception or Error',
$code_location,
$fq_class_name,
),
);
}
if (($fq_interface_name_lc === 'unitenum'
|| $fq_interface_name_lc === 'backedenum')
&& !$storage->is_enum
&& $codebase->analysis_php_version_id >= 8_01_00
) {
IssueBuffer::maybeAdd(
new InvalidInterfaceImplementation(
$fq_interface_name . ' cannot be implemented by classes',
$code_location,
$fq_class_name,
),
);
}
if ($interface_storage->deprecated) { if ($interface_storage->deprecated) {
IssueBuffer::maybeAdd( IssueBuffer::maybeAdd(
new DeprecatedInterface( new DeprecatedInterface(

View File

@ -0,0 +1,9 @@
<?php
namespace Psalm\Issue;
class InvalidInterfaceImplementation extends ClassIssue
{
const ERROR_LEVEL = -1;
const SHORTCODE = 317;
}

View File

@ -1202,6 +1202,14 @@ class ClassTest extends TestCase
', ',
'error_message' => 'MixedMethodCall', 'error_message' => 'MixedMethodCall',
], ],
'forbiddenThrowableImplementation' => [
'code' => '<?php
class C implements Throwable {}
',
'error_message' => 'InvalidInterfaceImplementation',
'ignored_issues' => [],
'php_version' => '7.0',
],
]; ];
} }
} }

View File

@ -308,6 +308,7 @@ class DocumentationTest extends TestCase
case 'InvalidEnumMethod': case 'InvalidEnumMethod':
case 'NoEnumProperties': case 'NoEnumProperties':
case 'OverriddenFinalConstant': case 'OverriddenFinalConstant':
case 'InvalidInterfaceImplementation':
$php_version = '8.1'; $php_version = '8.1';
break; break;
} }

View File

@ -765,6 +765,46 @@ class EnumTest extends TestCase
'ignored_issues' => [], 'ignored_issues' => [],
'php_version' => '8.1', 'php_version' => '8.1',
], ],
'forbiddenUnitEnumImplementation' => [
'code' => '<?php
class Foo implements UnitEnum {
/** @psalm-pure */
public static function cases(): array
{
return [];
}
}
',
'error_message' => 'InvalidInterfaceImplementation',
'ignored_issues' => [],
'php_version' => '8.1',
],
'forbiddenBackedEnumImplementation' => [
'code' => '<?php
class Foo implements BackedEnum {
/** @psalm-pure */
public static function cases(): array
{
return [];
}
/** @psalm-pure */
public static function from(int|string $value): static
{
throw new Exception;
}
/** @psalm-pure */
public static function tryFrom(int|string $value): ?static
{
return null;
}
}
',
'error_message' => 'InvalidInterfaceImplementation',
'ignored_issues' => [],
'php_version' => '8.1',
],
]; ];
} }
} }