Skip to content

Commit cd46553

Browse files
committed
address feedback
1 parent fde93f8 commit cd46553

File tree

5 files changed

+107
-13
lines changed

5 files changed

+107
-13
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1824,7 +1824,7 @@ parameters:
18241824
-
18251825
message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#'
18261826
identifier: phpstanApi.instanceofType
1827-
count: 3
1827+
count: 4
18281828
path: src/Type/TypeUtils.php
18291829

18301830
-

src/Analyser/NodeScopeResolver.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5139,21 +5139,20 @@ private function processArgs(
51395139
$scopeToPass = $closureBindScope;
51405140
}
51415141

5142-
if (
5143-
$parameterType === null
5144-
|| $parameterType instanceof MixedType
5145-
|| $parameterType->isCallable()->no()
5146-
) {
5147-
$callCallbackImmediately = false;
5148-
} elseif ($parameter instanceof ExtendedParameterReflection) {
5142+
$parameterCallableType = null;
5143+
if ($parameterType !== null) {
5144+
$parameterCallableType = TypeUtils::findCallableType($parameterType);
5145+
}
5146+
5147+
if ($parameter instanceof ExtendedParameterReflection) {
51495148
$parameterCallImmediately = $parameter->isImmediatelyInvokedCallable();
51505149
if ($parameterCallImmediately->maybe()) {
5151-
$callCallbackImmediately = $calleeReflection instanceof FunctionReflection;
5150+
$callCallbackImmediately = $parameterCallableType !== null && $calleeReflection instanceof FunctionReflection;
51525151
} else {
51535152
$callCallbackImmediately = $parameterCallImmediately->yes();
51545153
}
51555154
} else {
5156-
$callCallbackImmediately = $calleeReflection instanceof FunctionReflection;
5155+
$callCallbackImmediately = $parameterCallableType !== null && $calleeReflection instanceof FunctionReflection;
51575156
}
51585157

51595158
if ($arg->value instanceof Expr\Closure) {

src/Type/TypeUtils.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ public static function findThisType(Type $type): ?ThisType
170170
return null;
171171
}
172172

173+
public static function findCallableType(Type $type): ?Type
174+
{
175+
if ($type->isCallable()->yes()) {
176+
return $type;
177+
}
178+
179+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
180+
foreach ($type->getTypes() as $innerType) {
181+
$callableType = self::findCallableType($innerType);
182+
if ($callableType !== null) {
183+
return $callableType;
184+
}
185+
}
186+
}
187+
188+
return null;
189+
}
190+
173191
/**
174192
* @return HasPropertyType[]
175193
*/

tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ public function testRule(): void
116116
'Impure call to function array_push() in pure function PureFunction\bug13288().',
117117
182,
118118
],
119+
[
120+
'Impure exit in pure function PureFunction\bug13288b().',
121+
200,
122+
],
123+
[
124+
'Impure exit in pure function PureFunction\bug13288c().',
125+
217,
126+
],
127+
[
128+
'Impure exit in pure function PureFunction\bug13288d().',
129+
230,
130+
],
119131
]);
120132
}
121133

tests/PHPStan/Rules/Pure/data/pure-function.php

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,84 @@ function justContainsInlineHtml()
168168
/** @phpstan-pure */
169169
function bug13288(array $a)
170170
{
171-
array_push($a, function() { // error because by ref arg
171+
array_push($a, function () { // error because by ref arg
172172
exit(); // ok, as array_push() will not invoke the function
173173
});
174174

175175
array_push($a, // error because by ref arg
176176
fn() => exit() // ok, as array_push() will not invoke the function
177177
);
178178

179-
$closure = function() {
179+
$exitingClosure = function () {
180180
exit();
181181
};
182182
array_push($a, // error because by ref arg
183-
$closure // ok, as array_push() will not invoke the function
183+
$exitingClosure // ok, as array_push() will not invoke the function
184184
);
185+
186+
takesString("exit"); // ok, as the maybe callable type string is not typed with immediately-invoked-callable
187+
}
188+
189+
/** @phpstan-pure */
190+
function takesString(string $s) {
191+
}
192+
193+
/** @phpstan-pure */
194+
function bug13288b()
195+
{
196+
$exitingClosure = function () {
197+
exit();
198+
};
199+
200+
takesMixed($exitingClosure); // error because immediately invoked
201+
}
202+
203+
/**
204+
* @phpstan-pure
205+
* @param-immediately-invoked-callable $m
206+
*/
207+
function takesMixed(mixed $m) {
208+
}
209+
210+
/** @phpstan-pure */
211+
function bug13288c()
212+
{
213+
$exitingClosure = function () {
214+
exit();
215+
};
216+
217+
takesMaybeCallable($exitingClosure);
218+
}
219+
220+
/** @phpstan-pure */
221+
function takesMaybeCallable(?callable $c) { // arguments passed to functions are considered "immediately called" by default
222+
}
223+
224+
/** @phpstan-pure */
225+
function bug13288d()
226+
{
227+
$exitingClosure = function () {
228+
exit();
229+
};
230+
takesMaybeCallable2($exitingClosure);
231+
}
232+
233+
/** @phpstan-pure */
234+
function takesMaybeCallable2(?\Closure $c) { // Closures are considered "immediately called"
235+
}
236+
237+
/** @phpstan-pure */
238+
function bug13288e(MyClass $m)
239+
{
240+
$exitingClosure = function () {
241+
exit();
242+
};
243+
$m->takesMaybeCallable($exitingClosure);
244+
}
245+
246+
class MyClass {
247+
/** @phpstan-pure */
248+
function takesMaybeCallable(?callable $c) { // arguments passed to methods are considered "later called" by default
249+
}
185250
}
186251

0 commit comments

Comments
 (0)