Skip to content

Commit f22485e

Browse files
phpstan-botclaude
andcommitted
Use non-strict type checking for built-in function callables
Built-in PHP functions always perform implicit type coercion regardless of declare(strict_types=1), so Stringable objects should be accepted as string parameters even in strict mode. Instead of propagating the calling scope's $strictTypes, detect whether the callable is a built-in function via FunctionCallableVariant::isBuiltin() and use non-strict checking for its parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 45583d5 commit f22485e

3 files changed

Lines changed: 17 additions & 14 deletions

File tree

src/Reflection/Callables/FunctionCallableVariant.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,14 @@ public function getAsserts(): Assertions
179179
return $this->function->getAsserts();
180180
}
181181

182+
public function isBuiltin(): bool
183+
{
184+
$isBuiltin = $this->function->isBuiltin();
185+
if ($isBuiltin instanceof TrinaryLogic) {
186+
return $isBuiltin->yes();
187+
}
188+
189+
return $isBuiltin;
190+
}
191+
182192
}

src/Type/CallableType.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\PhpDocParser\Printer\Printer;
1515
use PHPStan\Reflection\Assertions;
1616
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
17+
use PHPStan\Reflection\Callables\FunctionCallableVariant;
1718
use PHPStan\Reflection\Callables\SimpleImpurePoint;
1819
use PHPStan\Reflection\Callables\SimpleThrowPoint;
1920
use PHPStan\Reflection\ClassMemberAccessAnswerer;
@@ -139,7 +140,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
139140
return $type->isAcceptedBy($this, $strictTypes);
140141
}
141142

142-
return $this->isSuperTypeOfInternal($type, true, $strictTypes)->toAcceptsResult();
143+
return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult();
143144
}
144145

145146
public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
@@ -148,10 +149,10 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
148149
return $type->isSubTypeOf($this);
149150
}
150151

151-
return $this->isSuperTypeOfInternal($type, false, true);
152+
return $this->isSuperTypeOfInternal($type, false);
152153
}
153154

154-
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny, bool $strictTypes): IsSuperTypeOfResult
155+
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult
155156
{
156157
$isCallable = new IsSuperTypeOfResult($type->isCallable(), []);
157158
if ($isCallable->no()) {
@@ -180,11 +181,12 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny, bool $
180181

181182
$variantsResult = null;
182183
foreach ($type->getCallableParametersAcceptors($scope) as $variant) {
184+
$isBuiltinCallable = $variant instanceof FunctionCallableVariant && $variant->isBuiltin();
183185
$variant = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$variant], false);
184186
if (!$variant instanceof CallableParametersAcceptor) {
185187
return IsSuperTypeOfResult::createNo([]);
186188
}
187-
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny, $strictTypes);
189+
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny, !$isBuiltinCallable);
188190
if ($variantsResult === null) {
189191
$variantsResult = $isSuperType;
190192
} else {

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2750,16 +2750,7 @@ public function testBug11619(): void
27502750
#[RequiresPhp('>= 8.1')]
27512751
public function testBug11619Strict(): void
27522752
{
2753-
$this->analyse([__DIR__ . '/data/bug-11619-strict.php'], [
2754-
[
2755-
'Parameter #2 $callback of function uasort expects callable(Bug11619Strict\Foo, Bug11619Strict\Foo): int, \'strnatcasecmp\' given.',
2756-
28,
2757-
],
2758-
[
2759-
'Parameter #2 $callback of function usort expects callable(Bug11619Strict\Foo, Bug11619Strict\Foo): int, \'strnatcasecmp\' given.',
2760-
29,
2761-
],
2762-
]);
2753+
$this->analyse([__DIR__ . '/data/bug-11619-strict.php'], []);
27632754
}
27642755

27652756
public function testBug13247(): void

0 commit comments

Comments
 (0)