1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Detect magic method signature mismatch on interfaces

Fixes #5786
This commit is contained in:
robchett 2023-11-09 11:30:36 +00:00
parent b775d297bb
commit 61f02d8889
4 changed files with 108 additions and 39 deletions

View File

@ -258,8 +258,6 @@ final class ClassAnalyzer extends ClassLikeAnalyzer
IssueBuffer::maybeAdd($docblock_issue);
}
$classlike_storage_provider = $codebase->classlike_storage_provider;
$parent_fq_class_name = $this->parent_fq_class_name;
if ($class instanceof PhpParser\Node\Stmt\Class_ && $class->extends && $parent_fq_class_name) {
@ -626,43 +624,7 @@ final class ClassAnalyzer extends ClassLikeAnalyzer
}
$pseudo_methods = $storage->pseudo_methods + $storage->pseudo_static_methods;
foreach ($pseudo_methods as $pseudo_method_name => $pseudo_method_storage) {
$pseudo_method_id = new MethodIdentifier(
$this->fq_class_name,
$pseudo_method_name,
);
$overridden_method_ids = $codebase->methods->getOverriddenMethodIds($pseudo_method_id);
if ($overridden_method_ids
&& $pseudo_method_name !== '__construct'
&& $pseudo_method_storage->location
) {
foreach ($overridden_method_ids as $overridden_method_id) {
$parent_method_storage = $codebase->methods->getStorage($overridden_method_id);
$overridden_fq_class_name = $overridden_method_id->fq_class_name;
$parent_storage = $classlike_storage_provider->get($overridden_fq_class_name);
MethodComparator::compare(
$codebase,
null,
$storage,
$parent_storage,
$pseudo_method_storage,
$parent_method_storage,
$this->fq_class_name,
$pseudo_method_storage->visibility ?: 0,
$storage->location ?: $pseudo_method_storage->location,
$storage->suppressed_issues,
true,
false,
);
}
}
}
MethodComparator::comparePseudoMethods($pseudo_methods, $this->fq_class_name, $codebase, $storage);
$event = new AfterClassLikeAnalysisEvent(
$class,

View File

@ -217,6 +217,10 @@ final class InterfaceAnalyzer extends ClassLikeAnalyzer
}
}
$pseudo_methods = $class_storage->pseudo_methods + $class_storage->pseudo_static_methods;
MethodComparator::comparePseudoMethods($pseudo_methods, $this->fq_class_name, $codebase, $class_storage);
$statements_analyzer = new StatementsAnalyzer($this, new NodeDataProvider());
$statements_analyzer->analyze($member_stmts, $interface_context, null, true);

View File

@ -238,6 +238,53 @@ final class MethodComparator
return null;
}
/**
* @param array<lowercase-string, MethodStorage> $pseudo_methods
*/
public static function comparePseudoMethods(
array $pseudo_methods,
string $fq_class_name,
Codebase $codebase,
ClassLikeStorage $class_storage,
): void {
foreach ($pseudo_methods as $pseudo_method_name => $pseudo_method_storage) {
$pseudo_method_id = new MethodIdentifier(
$fq_class_name,
$pseudo_method_name,
);
$overridden_method_ids = $codebase->methods->getOverriddenMethodIds($pseudo_method_id);
if ($overridden_method_ids
&& $pseudo_method_name !== '__construct'
&& $pseudo_method_storage->location
) {
foreach ($overridden_method_ids as $overridden_method_id) {
$parent_method_storage = $codebase->methods->getStorage($overridden_method_id);
$overridden_fq_class_name = $overridden_method_id->fq_class_name;
$parent_storage = $codebase->classlike_storage_provider->get($overridden_fq_class_name);
self::compare(
$codebase,
null,
$class_storage,
$parent_storage,
$pseudo_method_storage,
$parent_method_storage,
$fq_class_name,
$pseudo_method_storage->visibility ?: 0,
$class_storage->location ?: $pseudo_method_storage->location,
$class_storage->suppressed_issues,
true,
false,
);
}
}
}
}
/**
* @param string[] $suppressed_issues
*/

View File

@ -1164,6 +1164,62 @@ class MagicMethodAnnotationTest extends TestCase
}',
'error_message' => 'UndefinedVariable',
],
'MagicMethodReturnTypesCheckedForClasses' => [
'code' => '<?php
class A
{
public function a(int $className): int { return 0; }
}
/**
* @method stdClass a(int $a)
*/
class B extends A {}
',
'error_message' => 'ImplementedReturnTypeMismatch',
],
'MagicMethodParamTypesCheckedForClasses' => [
'code' => '<?php
class A
{
public function a(int $className): int { return 0; }
}
/**
* @method int a(string $a)
*/
class B extends A {}
',
'error_message' => 'ImplementedParamTypeMismatch',
],
'MagicMethodReturnTypesCheckedForInterfaces' => [
'code' => '<?php
interface A
{
public function a(int $className): int;
}
/**
* @method stdClass a(int $a)
*/
interface B extends A {}
',
'error_message' => 'ImplementedReturnTypeMismatch',
],
'MagicMethodParamTypesCheckedForInterfaces' => [
'code' => '<?php
interface A
{
public function a(string $className): int;
}
/**
* @method int a(int $a)
*/
interface B extends A {}
',
'error_message' => 'ImplementedParamTypeMismatch',
],
];
}