Skip to content
Merged
28 changes: 24 additions & 4 deletions src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use PHPStan\Reflection\MissingConstantFromReflectionException;
use PHPStan\Reflection\MissingMethodFromReflectionException;
use PHPStan\Reflection\MissingPropertyFromReflectionException;
use PHPStan\Reflection\TrivialParametersAcceptor;
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedMethodPrototypeReflection;
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedPropertyPrototypeReflection;
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
Expand Down Expand Up @@ -46,6 +45,7 @@
use function array_filter;
use function array_intersect_key;
use function array_map;
use function array_merge;
use function array_shift;
use function array_unique;
use function array_values;
Expand Down Expand Up @@ -1124,11 +1124,31 @@

public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
{
if ($this->isCallable()->no()) {
throw new ShouldNotHappenException();
$yesAcceptors = [];
$maybeAcceptors = [];

foreach ($this->types as $type) {
$isCallable = $type->isCallable();
if ($isCallable->no()) {

Check warning on line 1132 in src/Type/IntersectionType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ foreach ($this->types as $type) { $isCallable = $type->isCallable(); - if ($isCallable->no()) { + if (!$isCallable->yes()) { continue; }

Check warning on line 1132 in src/Type/IntersectionType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ foreach ($this->types as $type) { $isCallable = $type->isCallable(); - if ($isCallable->no()) { + if (!$isCallable->yes()) { continue; }
continue;
}

if ($isCallable->yes()) {
$yesAcceptors[] = $type->getCallableParametersAcceptors($scope);
} else {
$maybeAcceptors[] = $type->getCallableParametersAcceptors($scope);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to return early with return [new TrivialParametersAcceptor()]; on the first maybe-acceptor we find?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the earlier test run which confirmed all 11656 tests passed. Everything is already committed and pushed.

}
}

if (count($yesAcceptors) > 0) {
return array_merge(...$yesAcceptors);
Comment thread
staabm marked this conversation as resolved.
Outdated
}

if (count($maybeAcceptors) > 0) {
return array_merge(...$maybeAcceptors);
}

return [new TrivialParametersAcceptor()];
throw new ShouldNotHappenException();
}

public function isCloneable(): TrinaryLogic
Expand Down
70 changes: 70 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14362.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php // lint >= 8.1

declare(strict_types = 1);

namespace Bug14362;

use function PHPStan\Testing\assertType;

interface A
{
public function __invoke(B $b): int;
}

interface B
{

}

class C {
public static function u(): A&B {
Comment thread
staabm marked this conversation as resolved.
return new class() implements A, B {
public function __invoke(B $b): int {
return 1;
}
};
}
}

class D {
public static function u(): A {
return new class() implements A {
public function __invoke(B $b): int {
return 1;
}
};
}
}

interface E
{

}

class G {
public static function u(): A&B&E {
return new class() implements A, B, E {
public function __invoke(B $b): int {
return 1;
}
};
}
}

class H {
public static function u(): B&E {
return new class() implements B, E {
};
}
}

function () : void {
assertType('Closure(Bug14362\B): int', C::u()(...));
assertType('Closure(Bug14362\B): int', D::u()(...));

// Intersection with one yes-callable and multiple maybe-callable types
assertType('Closure(Bug14362\B): int', G::u()(...));

// Intersection with only maybe-callable types (neither has __invoke)
assertType('Closure', H::u()(...));
};
Loading