Skip to content

Commit ed784db

Browse files
committed
Merge remote-tracking branch 'origin/2.1.x' into 2.2.x
2 parents 9c3b6d5 + fb6208a commit ed784db

File tree

6 files changed

+151
-25
lines changed

6 files changed

+151
-25
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,21 @@ jobs:
436436
cd e2e/bug-14036
437437
composer install
438438
../../bin/phpstan analyze
439+
- script: |
440+
cd e2e/vvv-with-debug
441+
OUTPUT=$(../../bin/phpstan analyze -l 0 -vvv --debug test.php 2>&1)
442+
echo "$OUTPUT"
443+
../bashunit -a not_contains 'Parallel processing scheduler' "$OUTPUT"
444+
- script: |
445+
cd e2e/vvv-with-debug
446+
OUTPUT=$(../../bin/phpstan analyze -l 0 -vvv --debug --configuration parallel.neon test.php 2>&1)
447+
echo "$OUTPUT"
448+
../bashunit -a not_contains 'Parallel processing scheduler' "$OUTPUT"
449+
- script: |
450+
cd e2e/vvv-with-debug
451+
OUTPUT=$(../../bin/phpstan analyze -l 0 -vvv test.php 2>&1)
452+
echo "$OUTPUT"
453+
../bashunit -a contains 'Parallel processing scheduler' "$OUTPUT"
439454
440455
steps:
441456
- name: Harden the runner (Audit all outbound calls)

e2e/vvv-with-debug/parallel.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
parallel:
3+
maximumNumberOfProcesses: 2

e2e/vvv-with-debug/test.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php

src/Command/AnalyserRunner.php

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -71,30 +71,27 @@ public function runAnalyser(
7171
);
7272
}
7373

74-
$schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $files);
75-
$mainScript = null;
76-
if (isset($_SERVER['argv'][0]) && is_file($_SERVER['argv'][0])) {
77-
$mainScript = $_SERVER['argv'][0];
78-
}
74+
if (!$debug && $allowParallel && function_exists('proc_open')) {
75+
$schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $files);
76+
77+
$mainScript = null;
78+
if (isset($_SERVER['argv'][0]) && is_file($_SERVER['argv'][0])) {
79+
$mainScript = $_SERVER['argv'][0];
80+
}
7981

80-
if (
81-
!$debug
82-
&& $allowParallel
83-
&& function_exists('proc_open')
84-
&& $mainScript !== null
85-
&& $schedule->getNumberOfProcesses() > 0
86-
) {
87-
$loop = new StreamSelectLoop();
88-
$result = null;
89-
$promise = $this->parallelAnalyser->analyse($loop, $schedule, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input, null);
90-
$promise->then(static function (AnalyserResult $tmp) use (&$result): void {
91-
$result = $tmp;
92-
});
93-
$loop->run();
94-
if ($result === null) {
95-
throw new ShouldNotHappenException();
82+
if ($mainScript !== null && $schedule->getNumberOfProcesses() > 0) {
83+
$loop = new StreamSelectLoop();
84+
$result = null;
85+
$promise = $this->parallelAnalyser->analyse($loop, $schedule, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input, null);
86+
$promise->then(static function (AnalyserResult $tmp) use (&$result): void {
87+
$result = $tmp;
88+
});
89+
$loop->run();
90+
if ($result === null) {
91+
throw new ShouldNotHappenException();
92+
}
93+
return $result;
9694
}
97-
return $result;
9895
}
9996

10097
return $this->analyser->analyse(

src/Type/IntersectionType.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
namespace PHPStan\Type;
44

5+
use PHPStan\Internal\CombinationsHelper;
56
use PHPStan\Php\PhpVersion;
67
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
78
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
89
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
910
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
1011
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
12+
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
1113
use PHPStan\Reflection\ClassConstantReflection;
1214
use PHPStan\Reflection\ClassMemberAccessAnswerer;
1315
use PHPStan\Reflection\ExtendedMethodReflection;
@@ -16,6 +18,7 @@
1618
use PHPStan\Reflection\MissingConstantFromReflectionException;
1719
use PHPStan\Reflection\MissingMethodFromReflectionException;
1820
use PHPStan\Reflection\MissingPropertyFromReflectionException;
21+
use PHPStan\Reflection\ParametersAcceptorSelector;
1922
use PHPStan\Reflection\TrivialParametersAcceptor;
2023
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedMethodPrototypeReflection;
2124
use PHPStan\Reflection\Type\IntersectionTypeUnresolvedPropertyPrototypeReflection;
@@ -1131,11 +1134,34 @@ public function isCallable(): TrinaryLogic
11311134

11321135
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
11331136
{
1134-
if ($this->isCallable()->no()) {
1135-
throw new ShouldNotHappenException();
1137+
$yesAcceptors = [];
1138+
1139+
foreach ($this->types as $type) {
1140+
if (!$type->isCallable()->yes()) {
1141+
continue;
1142+
}
1143+
$yesAcceptors[] = $type->getCallableParametersAcceptors($scope);
1144+
}
1145+
1146+
if (count($yesAcceptors) === 0) {
1147+
if ($this->isCallable()->no()) {
1148+
throw new ShouldNotHappenException();
1149+
}
1150+
1151+
return [new TrivialParametersAcceptor()];
1152+
}
1153+
1154+
$result = [];
1155+
$combinations = CombinationsHelper::combinations($yesAcceptors);
1156+
foreach ($combinations as $combination) {
1157+
$combined = ParametersAcceptorSelector::combineAcceptors($combination);
1158+
if (!$combined instanceof CallableParametersAcceptor) {
1159+
throw new ShouldNotHappenException();
1160+
}
1161+
$result[] = $combined;
11361162
}
11371163

1138-
return [new TrivialParametersAcceptor()];
1164+
return $result;
11391165
}
11401166

11411167
public function isCloneable(): TrinaryLogic
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14362;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
interface A
10+
{
11+
public function __invoke(B $b): int;
12+
}
13+
14+
interface B
15+
{
16+
17+
}
18+
19+
class C {
20+
public static function u(): A&B {
21+
return new class() implements A, B {
22+
public function __invoke(B $b): int {
23+
return 1;
24+
}
25+
};
26+
}
27+
}
28+
29+
class D {
30+
public static function u(): A {
31+
return new class() implements A {
32+
public function __invoke(B $b): int {
33+
return 1;
34+
}
35+
};
36+
}
37+
}
38+
39+
interface E
40+
{
41+
public function __invoke(B $b, bool $option = true): int;
42+
}
43+
44+
interface F
45+
{
46+
47+
}
48+
49+
class G {
50+
public static function u(): A&E {
51+
return new class() implements A, E {
52+
public function __invoke(B $b, bool $option = true): int {
53+
return 1;
54+
}
55+
};
56+
}
57+
}
58+
59+
class H {
60+
public static function u(): B&F {
61+
return new class() implements B, F {
62+
};
63+
}
64+
}
65+
66+
function doBar() : void {
67+
assertType('Closure(Bug14362\B): int', C::u()(...));
68+
assertType('Closure(Bug14362\B): int', D::u()(...));
69+
70+
// Intersection with two yes-callable compatible
71+
assertType('Closure(Bug14362\B, bool=): int', G::u()(...));
72+
73+
// Intersection with only maybe-callable types (neither has __invoke)
74+
assertType('Closure', H::u()(...));
75+
}
76+
77+
function doFoo(string $c):void {
78+
if (is_callable($c)) {
79+
$a = $c;
80+
} else {
81+
$a = C::u()(...);
82+
}
83+
assertType('callable-string|(Closure(Bug14362\B): int)', $a);
84+
}

0 commit comments

Comments
 (0)