Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ lint:
--exclude tests/PHPStan/Rules/Variables/data/bug-14349.php \
--exclude tests/PHPStan/Rules/Variables/data/bug-14352.php \
--exclude tests/PHPStan/Rules/Variables/data/bug-14351.php \
--exclude tests/PHPStan/Rules/Properties/data/bug-14457.php \
src tests

install-paratest:
Expand Down
33 changes: 33 additions & 0 deletions src/Rules/Methods/OverridingMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
->build(),
], $node, $scope);
}
$parentAbstract = $parentConstructor->isAbstract();
if (
$method->isAbstract()->yes()
&& !(is_bool($parentAbstract) ? $parentAbstract : $parentAbstract->yes())
) {
return $this->addErrors([
RuleErrorBuilder::message(sprintf(
'Cannot make non-abstract method %s::%s() abstract in class %s.',
$parentConstructor->getDeclaringClass()->getDisplayName(true),
$parentConstructor->getName(),
$method->getDeclaringClass()->getDisplayName(),
))
->nonIgnorable()
->identifier('method.abstractOverridingNonAbstract')
->build(),
], $node, $scope);
}
}
}

Expand Down Expand Up @@ -188,6 +205,22 @@ public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array
->build();
}

$prototypeAbstract = $prototype->isAbstract();
if (
$method->isAbstract()->yes()
&& !(is_bool($prototypeAbstract) ? $prototypeAbstract : $prototypeAbstract->yes())
) {
$messages[] = RuleErrorBuilder::message(sprintf(
'Cannot make non-abstract method %s::%s() abstract in class %s.',
$prototypeDeclaringClass->getDisplayName(true),
$prototype->getName(),
$method->getDeclaringClass()->getDisplayName(),
))
->nonIgnorable()
->identifier('method.abstractOverridingNonAbstract')
->build();
}

if ($checkVisibility) {
$messages = array_merge($messages, $this->methodVisibilityComparisonHelper->compare($prototype, $prototypeDeclaringClass, $method));
}
Expand Down
17 changes: 17 additions & 0 deletions src/Rules/Properties/OverridingPropertyRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,23 @@
))->identifier('property.notWritable')->nonIgnorable()->build();
}
}
if ($node->isAbstract() && $prototype->isAbstract()->no()) {

Check warning on line 181 in src/Rules/Properties/OverridingPropertyRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ ))->identifier('property.notWritable')->nonIgnorable()->build(); } } - if ($node->isAbstract() && $prototype->isAbstract()->no()) { + if ($node->isAbstract() && !$prototype->isAbstract()->yes()) { foreach (['get', 'set'] as $hookType) { if (!$propertyReflection->hasHook($hookType) || !$prototype->hasHook($hookType)) { continue;

Check warning on line 181 in src/Rules/Properties/OverridingPropertyRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ ))->identifier('property.notWritable')->nonIgnorable()->build(); } } - if ($node->isAbstract() && $prototype->isAbstract()->no()) { + if ($node->isAbstract() && !$prototype->isAbstract()->yes()) { foreach (['get', 'set'] as $hookType) { if (!$propertyReflection->hasHook($hookType) || !$prototype->hasHook($hookType)) { continue;
foreach (['get', 'set'] as $hookType) {
if (!$propertyReflection->hasHook($hookType) || !$prototype->hasHook($hookType)) {
continue;
}
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot make non-abstract method %s::$%s::%s() abstract in class %s.',
$prototype->getDeclaringClass()->getDisplayName(true),
$node->getName(),
$hookType,
$classReflection->getDisplayName(),
))
->nonIgnorable()
->identifier('property.abstractOverridingNonAbstractHook')
->build();
}
}
}

if ($prototype->isPublic()) {
Expand Down
27 changes: 27 additions & 0 deletions tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -853,4 +853,31 @@ public function testFixWithTabs(): void
$this->fix(__DIR__ . '/data/fix-with-tabs.php', __DIR__ . '/data/fix-with-tabs.php.fixed');
}

public function testBug14457(): void
{
$this->phpVersionId = PHP_VERSION_ID;
$this->analyse([__DIR__ . '/data/bug-14457.php'], [
[
'Cannot make non-abstract method Bug14457\ParentClass::foo() abstract in class Bug14457\ChildClass.',
12,
],
[
'Cannot make non-abstract method Bug14457\StaticParent::staticMethod() abstract in class Bug14457\StaticChild.',
56,
],
[
'Cannot make non-abstract method Bug14457\ProtectedParent::protectedMethod() abstract in class Bug14457\ProtectedChild.',
67,
],
[
'Cannot make non-abstract method Bug14457\GrandParent_::inherited() abstract in class Bug14457\Child_.',
81,
],
[
'Cannot make non-abstract method Bug14457\ConstructorParent::__construct() abstract in class Bug14457\ConstructorChild.',
91,
],
]);
}

}
101 changes: 101 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-14457.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php // lint >= 8.0

namespace Bug14457;

abstract class ParentClass {
public function foo(): int {
return 42;
}
}

abstract class ChildClass extends ParentClass {
abstract public function foo(): int;
}

// OK: abstract method overriding abstract method
abstract class AbstractParent {
abstract public function bar(): int;
}

abstract class AbstractChild extends AbstractParent {
abstract public function bar(): int;
}

// OK: non-abstract method overriding non-abstract method
abstract class ConcreteParent {
public function baz(): int {
return 1;
}
}

abstract class ConcreteChild extends ConcreteParent {
public function baz(): int {
return 2;
}
}

// OK: non-abstract method overriding abstract method (implementing it)
abstract class AbstractParent2 {
abstract public function qux(): int;
}

abstract class ConcreteChild2 extends AbstractParent2 {
public function qux(): int {
return 1;
}
}

// abstract static method overriding non-abstract static method
abstract class StaticParent {
public static function staticMethod(): int {
return 1;
}
}

abstract class StaticChild extends StaticParent {
abstract public static function staticMethod(): int;
}

// abstract protected method overriding non-abstract protected method
abstract class ProtectedParent {
protected function protectedMethod(): int {
return 1;
}
}

abstract class ProtectedChild extends ProtectedParent {
abstract protected function protectedMethod(): int;
}

// multiple levels of inheritance
abstract class GrandParent_ {
public function inherited(): int {
return 1;
}
}

abstract class Parent_ extends GrandParent_ {
}

abstract class Child_ extends Parent_ {
abstract public function inherited(): int;
}

// abstract constructor overriding non-abstract constructor
abstract class ConstructorParent {
public function __construct() {
}
}

abstract class ConstructorChild extends ConstructorParent {
abstract public function __construct();
}

// OK: abstract constructor overriding abstract constructor
abstract class AbstractConstructorParent {
abstract public function __construct();
}

abstract class AbstractConstructorChild extends AbstractConstructorParent {
abstract public function __construct();
}
24 changes: 24 additions & 0 deletions tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,28 @@ public function testFixMissingOverrideAttribute(): void
$this->fix(__DIR__ . '/data/property-override-attr-missing.php', __DIR__ . '/data/property-override-attr-missing.php.fixed');
}

#[RequiresPhp('>= 8.4.0')]
public function testBug14457(): void
{
$this->reportMaybes = true;
$this->analyse([__DIR__ . '/data/bug-14457.php'], [
[
'Cannot make non-abstract method Bug14457Property\ParentClass::$bar::get() abstract in class Bug14457Property\ChildClass.',
10,
],
[
'Cannot make non-abstract method Bug14457Property\SetParent::$setProp::set() abstract in class Bug14457Property\SetChild.',
42,
],
[
'Cannot make non-abstract method Bug14457Property\BothParent::$bothProp::get() abstract in class Bug14457Property\BothChild.',
54,
],
[
'Cannot make non-abstract method Bug14457Property\BothParent::$bothProp::set() abstract in class Bug14457Property\BothChild.',
54,
],
]);
}

}
55 changes: 55 additions & 0 deletions tests/PHPStan/Rules/Properties/data/bug-14457.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php // lint >= 8.4

namespace Bug14457Property;

abstract class ParentClass {
public int $bar { get => 42; }
}

abstract class ChildClass extends ParentClass {
abstract public int $bar { get; }
}

// OK: abstract hook overriding abstract hook
abstract class AbstractParent {
abstract public int $prop { get; }
}

abstract class AbstractChild extends AbstractParent {
abstract public int $prop { get; }
}

// OK: concrete hook overriding abstract hook
abstract class ConcreteChild extends AbstractParent {
public int $prop { get => 1; }
}

// OK: concrete hook overriding concrete hook
abstract class ConcreteParent {
public int $val { get => 1; }
}

abstract class ConcreteChild2 extends ConcreteParent {
public int $val { get => 2; }
}

// abstract set hook overriding non-abstract set hook
abstract class SetParent {
public int $setProp { set => $value; }
}

abstract class SetChild extends SetParent {
abstract public int $setProp { set; }
}

// both get and set hooks abstract overriding non-abstract
abstract class BothParent {
public int $bothProp {
get => 1;
set => $value;
}
}

abstract class BothChild extends BothParent {
abstract public int $bothProp { get; set; }
}
Loading