Skip to content

Commit

Permalink
Forbid implementing some interfaces
Browse files Browse the repository at this point in the history
- `Throwable` can only be implemented when classes extend one of
  `Exception` or `Error`
- `UnitEnum` and `BackedEnum` cannot be implemented by user-defined
  classes

Refs #7722
  • Loading branch information
weirdan committed Feb 12, 2023
1 parent 0fb0714 commit 9e3c255
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions config.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
<xs:element name="InvalidFalsableReturnType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidFunctionCall" 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="InvalidLiteralArgument" type="ArgumentIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidMethodCall" type="IssueHandlerType" minOccurs="0" />
Expand Down
1 change: 1 addition & 0 deletions docs/running_psalm/error_levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidInterfaceImplementation](issues/InvalidInterfaceImplementation.md)
- [InvalidParamDefault](issues/InvalidParamDefault.md)
- [InvalidParent](issues/InvalidParent.md)
- [InvalidPassByReference](issues/InvalidPassByReference.md)
Expand Down
1 change: 1 addition & 0 deletions docs/running_psalm/issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
- [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md)
- [InvalidFunctionCall](issues/InvalidFunctionCall.md)
- [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidInterfaceImplementation](issues/InvalidInterfaceImplementation.md)
- [InvalidIterator](issues/InvalidIterator.md)
- [InvalidLiteralArgument](issues/InvalidLiteralArgument.md)
- [InvalidMethodCall](issues/InvalidMethodCall.md)
Expand Down
15 changes: 15 additions & 0 deletions docs/running_psalm/issues/InvalidInterfaceImplementation.md
Original file line number Diff line number Diff line change
@@ -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 [];
}
}
```
30 changes: 30 additions & 0 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use Psalm\Issue\InternalClass;
use Psalm\Issue\InvalidEnumCaseValue;
use Psalm\Issue\InvalidExtendClass;
use Psalm\Issue\InvalidInterfaceImplementation;
use Psalm\Issue\InvalidTraversableImplementation;
use Psalm\Issue\MethodSignatureMismatch;
use Psalm\Issue\MismatchingDocblockPropertyType;
Expand Down Expand Up @@ -2114,6 +2115,35 @@ private function checkImplementedInterfaces(
);
}

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) {
IssueBuffer::maybeAdd(
new DeprecatedInterface(
Expand Down
9 changes: 9 additions & 0 deletions src/Psalm/Issue/InvalidInterfaceImplementation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Psalm\Issue;

class InvalidInterfaceImplementation extends ClassIssue
{
const ERROR_LEVEL = -1;
const SHORTCODE = 317;
}
8 changes: 8 additions & 0 deletions tests/ClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,14 @@ function f(object $o): object
',
'error_message' => 'MixedMethodCall',
],
'forbiddenThrowableImplementation' => [
'code' => '<?php
class C implements Throwable {}
',
'error_message' => 'InvalidInterfaceImplementation',
'ignored_issues' => [],
'php_version' => '7.0',
],
];
}
}
1 change: 1 addition & 0 deletions tests/DocumentationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ public function providerInvalidCodeParse(): array
case 'InvalidEnumMethod':
case 'NoEnumProperties':
case 'OverriddenFinalConstant':
case 'InvalidInterfaceImplementation':
$php_version = '8.1';
break;
}
Expand Down
40 changes: 40 additions & 0 deletions tests/EnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,46 @@ public function __get() {}
'ignored_issues' => [],
'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',
]
];
}
}

0 comments on commit 9e3c255

Please sign in to comment.