Skip to content

Commit 627c741

Browse files
phpstan-botclaude
andcommitted
Check if the calling function is builtin, not the callback
The non-error on `uasort($options, 'strnatcasecmp')` in strict mode is because uasort (the caller) is builtin, not because strnatcasecmp (the callback) is builtin. Per PHP internals, when a builtin function calls a callback, it always uses strict_types=0. - Remove isBuiltin() from CallableParametersAcceptor interface and all implementations (was checking the wrong function) - Forward $strictTypes through CallableType/ClosureType accepts() to CallableTypeHelper - In FunctionCallParametersCheck, set strictTypes=false for callable parameters of builtin functions - Update tests: customUsort (user-defined) in strict mode now correctly reports an error, while uasort/usort (builtin) do not Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ef20977 commit 627c741

12 files changed

Lines changed: 25 additions & 57 deletions

src/Reflection/Callables/CallableParametersAcceptor.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,4 @@ public function mustUseReturnValue(): TrinaryLogic;
6060

6161
public function getAsserts(): Assertions;
6262

63-
public function isBuiltin(): TrinaryLogic;
64-
6563
}

src/Reflection/Callables/FunctionCallableVariant.php

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

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

src/Reflection/ExtendedCallableFunctionVariant.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public function __construct(
3737
private array $usedVariables,
3838
private TrinaryLogic $acceptsNamedArguments,
3939
private TrinaryLogic $mustUseReturnValue,
40-
private TrinaryLogic $isBuiltinCallable,
4140
private ?Assertions $assertions = null,
4241
)
4342
{
@@ -93,9 +92,4 @@ public function getAsserts(): Assertions
9392
return $this->assertions ?? Assertions::createEmpty();
9493
}
9594

96-
public function isBuiltin(): TrinaryLogic
97-
{
98-
return $this->isBuiltinCallable;
99-
}
100-
10195
}

src/Reflection/GenericParametersAcceptorResolver.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc
130130
$originalParametersAcceptor->getUsedVariables(),
131131
$originalParametersAcceptor->acceptsNamedArguments(),
132132
$originalParametersAcceptor->mustUseReturnValue(),
133-
$originalParametersAcceptor->isBuiltin(),
134133
$originalParametersAcceptor->getAsserts(),
135134
);
136135
}

src/Reflection/InaccessibleMethod.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,4 @@ public function getAsserts(): Assertions
9898
return Assertions::createEmpty();
9999
}
100100

101-
public function isBuiltin(): TrinaryLogic
102-
{
103-
return TrinaryLogic::createMaybe();
104-
}
105-
106101
}

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,6 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
736736
$usedVariables = [];
737737
$acceptsNamedArguments = TrinaryLogic::createNo();
738738
$mustUseReturnValue = TrinaryLogic::createMaybe();
739-
$isBuiltin = TrinaryLogic::createMaybe();
740739

741740
foreach ($acceptors as $acceptor) {
742741
$returnTypes[] = $acceptor->getReturnType();
@@ -754,7 +753,6 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
754753
$usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables());
755754
$acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments());
756755
$mustUseReturnValue = $mustUseReturnValue->or($acceptor->mustUseReturnValue());
757-
$isBuiltin = $isBuiltin->or($acceptor->isBuiltin());
758756
}
759757
$isVariadic = $isVariadic || $acceptor->isVariadic();
760758

@@ -862,7 +860,6 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
862860
$usedVariables,
863861
$acceptsNamedArguments,
864862
$mustUseReturnValue,
865-
$isBuiltin,
866863
);
867864
}
868865

@@ -900,7 +897,6 @@ private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedPara
900897
$acceptor->getUsedVariables(),
901898
$acceptor->acceptsNamedArguments(),
902899
$acceptor->mustUseReturnValue(),
903-
$acceptor->isBuiltin(),
904900
$acceptor->getAsserts(),
905901
);
906902
}

src/Reflection/ResolvedFunctionVariantWithCallable.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public function __construct(
2929
private array $usedVariables,
3030
private TrinaryLogic $acceptsNamedArguments,
3131
private TrinaryLogic $mustUseReturnValue,
32-
private TrinaryLogic $isBuiltinCallable,
3332
private ?Assertions $assertions = null,
3433
)
3534
{
@@ -125,9 +124,4 @@ public function getAsserts(): Assertions
125124
return $this->assertions ?? Assertions::createEmpty();
126125
}
127126

128-
public function isBuiltin(): TrinaryLogic
129-
{
130-
return $this->isBuiltinCallable;
131-
}
132-
133127
}

src/Reflection/TrivialParametersAcceptor.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,4 @@ public function getAsserts(): Assertions
108108
return Assertions::createEmpty();
109109
}
110110

111-
public function isBuiltin(): TrinaryLogic
112-
{
113-
return TrinaryLogic::createMaybe();
114-
}
115-
116111
}

src/Rules/FunctionCallParametersCheck.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,11 @@ public function check(
363363
!$parameter->passedByReference()->createsNewVariable()
364364
|| (!$isBuiltin && !$argumentValueType instanceof ErrorType)
365365
) {
366-
$accepts = $this->ruleLevelHelper->accepts($parameterType, $argumentValueType, $scope->isDeclareStrictTypes());
366+
$callableStrictTypes = $scope->isDeclareStrictTypes();
367+
if ($isBuiltin && $parameterType->isCallable()->yes()) {
368+
$callableStrictTypes = false;
369+
}
370+
$accepts = $this->ruleLevelHelper->accepts($parameterType, $argumentValueType, $callableStrictTypes);
367371

368372
if (!$accepts->result) {
369373
$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $argumentValueType);

src/Type/CallableType.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
139139
return $type->isAcceptedBy($this, $strictTypes);
140140
}
141141

142-
return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult();
142+
return $this->isSuperTypeOfInternal($type, true, $strictTypes)->toAcceptsResult();
143143
}
144144

145145
public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
@@ -151,7 +151,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
151151
return $this->isSuperTypeOfInternal($type, false);
152152
}
153153

154-
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult
154+
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny, bool $strictTypes = true): IsSuperTypeOfResult
155155
{
156156
$isCallable = new IsSuperTypeOfResult($type->isCallable(), []);
157157
if ($isCallable->no()) {
@@ -180,12 +180,11 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSup
180180

181181
$variantsResult = null;
182182
foreach ($type->getCallableParametersAcceptors($scope) as $variant) {
183-
$isBuiltinCallable = $variant->isBuiltin()->yes();
184183
$variant = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$variant], false);
185184
if (!$variant instanceof CallableParametersAcceptor) {
186185
return IsSuperTypeOfResult::createNo([]);
187186
}
188-
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny, !$isBuiltinCallable);
187+
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny, $strictTypes);
189188
if ($variantsResult === null) {
190189
$variantsResult = $isSuperType;
191190
} else {
@@ -405,11 +404,6 @@ public function getAsserts(): Assertions
405404
return Assertions::createEmpty();
406405
}
407406

408-
public function isBuiltin(): TrinaryLogic
409-
{
410-
return TrinaryLogic::createMaybe();
411-
}
412-
413407
public function toNumber(): Type
414408
{
415409
return new ErrorType();

0 commit comments

Comments
 (0)