diff --git a/config.xsd b/config.xsd
index 00caa5508..5b4744f49 100644
--- a/config.xsd
+++ b/config.xsd
@@ -264,6 +264,7 @@
+
diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md
index 9eeb12e7f..a0799b892 100644
--- a/docs/running_psalm/error_levels.md
+++ b/docs/running_psalm/error_levels.md
@@ -45,6 +45,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [InaccessibleProperty](issues/InaccessibleProperty.md)
- [InterfaceInstantiation](issues/InterfaceInstantiation.md)
- [InvalidAttribute](issues/InvalidAttribute.md)
+ - [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidParamDefault](issues/InvalidParamDefault.md)
diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md
index 6729a85fe..d84f2bf3c 100644
--- a/docs/running_psalm/issues.md
+++ b/docs/running_psalm/issues.md
@@ -69,6 +69,7 @@
- [InvalidDocblockParamName](issues/InvalidDocblockParamName.md)
- [InvalidEnumBackingType](issues/InvalidEnumBackingType.md)
- [InvalidEnumCaseValue](issues/InvalidEnumCaseValue.md)
+ - [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md)
- [InvalidFunctionCall](issues/InvalidFunctionCall.md)
diff --git a/docs/running_psalm/issues/InvalidEnumMethod.md b/docs/running_psalm/issues/InvalidEnumMethod.md
new file mode 100644
index 000000000..575cd6b77
--- /dev/null
+++ b/docs/running_psalm/issues/InvalidEnumMethod.md
@@ -0,0 +1,15 @@
+# InvalidEnumMethod
+
+Enums may not define most of the magic methods like `__get`, `__toString`, etc.
+
+```php
+is_enum) {
+ MethodAnalyzer::checkForbiddenEnumMethod($storage);
+ }
+
if (!$context->calling_method_id || !$context->collect_initializations) {
$context->calling_method_id = strtolower((string)$method_id);
}
diff --git a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php
index 9ade34375..e11abef51 100644
--- a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php
@@ -9,6 +9,7 @@ use Psalm\Codebase;
use Psalm\Context;
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\MethodIdentifier;
+use Psalm\Issue\InvalidEnumMethod;
use Psalm\Issue\InvalidStaticInvocation;
use Psalm\Issue\MethodSignatureMustOmitReturnType;
use Psalm\Issue\NonStaticSelfCall;
@@ -27,6 +28,24 @@ use function strtolower;
*/
class MethodAnalyzer extends FunctionLikeAnalyzer
{
+ // https://github.com/php/php-src/blob/a83923044c48982c80804ae1b45e761c271966d3/Zend/zend_enum.c#L77-L95
+ private const FORBIDDEN_ENUM_METHODS = [
+ '__construct',
+ '__destruct',
+ '__clone',
+ '__get',
+ '__set',
+ '__unset',
+ '__isset',
+ '__tostring',
+ '__debuginfo',
+ '__serialize',
+ '__unserialize',
+ '__sleep',
+ '__wakeup',
+ '__set_state',
+ ];
+
/** @psalm-external-mutation-free */
public function __construct(
PhpParser\Node\Stmt\ClassMethod $function,
@@ -266,13 +285,17 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
return;
}
- $cased_method_name = $method_storage->cased_name;
+ if ($method_storage->cased_name === null) {
+ return;
+ }
+
+ $method_name_lc = strtolower($method_storage->cased_name);
$methodsOfInterest = ['__clone', '__construct', '__destruct'];
- if (in_array($cased_method_name, $methodsOfInterest)) {
+ if (in_array($method_name_lc, $methodsOfInterest, true)) {
IssueBuffer::maybeAdd(
new MethodSignatureMustOmitReturnType(
- 'Method ' . $cased_method_name . ' must not declare a return type',
+ 'Method ' . $method_storage->cased_name . ' must not declare a return type',
$code_location
)
);
@@ -288,4 +311,20 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
strtolower($function_name)
);
}
+
+ public static function checkForbiddenEnumMethod(MethodStorage $method_storage): void
+ {
+ if ($method_storage->cased_name === null || $method_storage->location === null) {
+ return;
+ }
+
+ $method_name_lc = strtolower($method_storage->cased_name);
+ if (in_array($method_name_lc, self::FORBIDDEN_ENUM_METHODS, true)) {
+ IssueBuffer::maybeAdd(new InvalidEnumMethod(
+ 'Enums cannot define ' . $method_storage->cased_name,
+ $method_storage->location,
+ $method_storage->defining_fqcln . '::' . $method_storage->cased_name
+ ));
+ }
+ }
}
diff --git a/src/Psalm/Issue/InvalidEnumMethod.php b/src/Psalm/Issue/InvalidEnumMethod.php
new file mode 100644
index 000000000..7b0d35581
--- /dev/null
+++ b/src/Psalm/Issue/InvalidEnumMethod.php
@@ -0,0 +1,9 @@
+ [],
'php_version' => '8.1',
],
+ 'forbiddenMethod' => [
+ 'code' => ' 'InvalidEnumMethod',
+ 'ignored_issues' => [],
+ 'php_version' => '8.1',
+ ],
];
}
}