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

Fix #2665 - warn about abstract class interface inheritance issues

This commit is contained in:
Brown 2020-01-21 11:46:51 -05:00
parent a2e1dfd64e
commit 0132b3789b
2 changed files with 155 additions and 135 deletions

View File

@ -488,149 +488,147 @@ class ClassAnalyzer extends ClassLikeAnalyzer
$class_interfaces = $storage->class_implements;
if (!$class->isAbstract()) {
foreach ($class_interfaces as $interface_name) {
try {
$interface_storage = $classlike_storage_provider->get($interface_name);
} catch (\InvalidArgumentException $e) {
continue;
foreach ($class_interfaces as $interface_name) {
try {
$interface_storage = $classlike_storage_provider->get($interface_name);
} catch (\InvalidArgumentException $e) {
continue;
}
$code_location = new CodeLocation(
$this,
$class->name ? $class->name : $class,
$class_context ? $class_context->include_location : null,
true
);
if ($interface_storage->deprecated) {
if (IssueBuffer::accepts(
new DeprecatedInterface(
$interface_name . ' is marked deprecated',
$code_location,
$interface_name
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
}
$code_location = new CodeLocation(
$this,
$class->name ? $class->name : $class,
$class_context ? $class_context->include_location : null,
true
);
if ($interface_storage->deprecated) {
if (IssueBuffer::accepts(
new DeprecatedInterface(
$interface_name . ' is marked deprecated',
$code_location,
$interface_name
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
if ($interface_storage->external_mutation_free
&& !$storage->external_mutation_free
) {
if (IssueBuffer::accepts(
new MissingImmutableAnnotation(
$interface_name . ' is marked immutable, but '
. $fq_class_name . ' is not marked immutable',
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
}
if ($interface_storage->external_mutation_free
&& !$storage->external_mutation_free
) {
if (IssueBuffer::accepts(
new MissingImmutableAnnotation(
$interface_name . ' is marked immutable, but '
. $fq_class_name . ' is not marked immutable',
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
}
foreach ($interface_storage->methods as $method_name => $interface_method_storage) {
if ($interface_method_storage->visibility === self::VISIBILITY_PUBLIC) {
$implementer_declaring_method_id = $codebase->methods->getDeclaringMethodId(
$this->fq_class_name . '::' . $method_name
);
foreach ($interface_storage->methods as $method_name => $interface_method_storage) {
if ($interface_method_storage->visibility === self::VISIBILITY_PUBLIC) {
$implementer_declaring_method_id = $codebase->methods->getDeclaringMethodId(
$this->fq_class_name . '::' . $method_name
$implementer_method_storage = null;
$implementer_classlike_storage = null;
if ($implementer_declaring_method_id) {
list($implementer_fq_class_name) = explode('::', $implementer_declaring_method_id);
$implementer_method_storage = $codebase->methods->getStorage(
$implementer_declaring_method_id
);
$implementer_method_storage = null;
$implementer_classlike_storage = null;
if ($implementer_declaring_method_id) {
list($implementer_fq_class_name) = explode('::', $implementer_declaring_method_id);
$implementer_method_storage = $codebase->methods->getStorage(
$implementer_declaring_method_id
);
$implementer_classlike_storage = $classlike_storage_provider->get(
$implementer_fq_class_name
);
}
if (!$implementer_method_storage) {
if (IssueBuffer::accepts(
new UnimplementedInterfaceMethod(
'Method ' . $method_name . ' is not defined on class ' .
$storage->name,
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
return false;
}
return null;
}
$implementer_appearing_method_id = $codebase->methods->getAppearingMethodId(
$this->fq_class_name . '::' . $method_name
);
$implementer_visibility = $implementer_method_storage->visibility;
if ($implementer_appearing_method_id
&& $implementer_appearing_method_id !== $implementer_declaring_method_id
) {
list($appearing_fq_class_name, $appearing_method_name) = explode(
'::',
$implementer_appearing_method_id
);
$appearing_class_storage = $classlike_storage_provider->get(
$appearing_fq_class_name
);
if (isset($appearing_class_storage->trait_visibility_map[$appearing_method_name])) {
$implementer_visibility
= $appearing_class_storage->trait_visibility_map[$appearing_method_name];
}
}
if ($implementer_visibility !== self::VISIBILITY_PUBLIC) {
if (IssueBuffer::accepts(
new InaccessibleMethod(
'Interface-defined method ' . $implementer_method_storage->cased_name
. ' must be public in ' . $storage->name,
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
return false;
}
return null;
}
if ($interface_method_storage->is_static && !$implementer_method_storage->is_static) {
if (IssueBuffer::accepts(
new MethodSignatureMismatch(
'Method ' . $implementer_method_storage->cased_name
. ' should be static like '
. $storage->name . '::' . $interface_method_storage->cased_name,
$code_location
),
$implementer_method_storage->suppressed_issues
)) {
return false;
}
}
MethodAnalyzer::compareMethods(
$codebase,
$implementer_classlike_storage ?: $storage,
$interface_storage,
$implementer_method_storage,
$interface_method_storage,
$this->fq_class_name,
$implementer_visibility,
$code_location,
$implementer_method_storage->suppressed_issues,
false
$implementer_classlike_storage = $classlike_storage_provider->get(
$implementer_fq_class_name
);
}
if (!$implementer_method_storage) {
if (IssueBuffer::accepts(
new UnimplementedInterfaceMethod(
'Method ' . $method_name . ' is not defined on class ' .
$storage->name,
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
return false;
}
return null;
}
$implementer_appearing_method_id = $codebase->methods->getAppearingMethodId(
$this->fq_class_name . '::' . $method_name
);
$implementer_visibility = $implementer_method_storage->visibility;
if ($implementer_appearing_method_id
&& $implementer_appearing_method_id !== $implementer_declaring_method_id
) {
list($appearing_fq_class_name, $appearing_method_name) = explode(
'::',
$implementer_appearing_method_id
);
$appearing_class_storage = $classlike_storage_provider->get(
$appearing_fq_class_name
);
if (isset($appearing_class_storage->trait_visibility_map[$appearing_method_name])) {
$implementer_visibility
= $appearing_class_storage->trait_visibility_map[$appearing_method_name];
}
}
if ($implementer_visibility !== self::VISIBILITY_PUBLIC) {
if (IssueBuffer::accepts(
new InaccessibleMethod(
'Interface-defined method ' . $implementer_method_storage->cased_name
. ' must be public in ' . $storage->name,
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
return false;
}
return null;
}
if ($interface_method_storage->is_static && !$implementer_method_storage->is_static) {
if (IssueBuffer::accepts(
new MethodSignatureMismatch(
'Method ' . $implementer_method_storage->cased_name
. ' should be static like '
. $storage->name . '::' . $interface_method_storage->cased_name,
$code_location
),
$implementer_method_storage->suppressed_issues
)) {
return false;
}
}
MethodAnalyzer::compareMethods(
$codebase,
$implementer_classlike_storage ?: $storage,
$interface_storage,
$implementer_method_storage,
$interface_method_storage,
$this->fq_class_name,
$implementer_visibility,
$code_location,
$implementer_method_storage->suppressed_issues,
false
);
}
}
}

View File

@ -1214,6 +1214,28 @@ class MethodSignatureTest extends TestCase
}',
'error_message' => 'TraitMethodSignatureMismatch',
],
'abstractClassReturnMismatch' => [
'<?php
interface I {
function foo(): array;
}
abstract class C implements I {
public function foo(): void {}
}',
'error_message' => 'MethodSignatureMismatch',
],
'abstractClassParamMismatch' => [
'<?php
interface I {
function foo(int $s): void;
}
abstract class C implements I {
public function foo(string $s): void {}
}',
'error_message' => 'MethodSignatureMismatch',
],
];
}
}