Skip to content

Commit befcc3e

Browse files
ondrejmirtesphpstan-bot
authored andcommitted
Fix callable parameter checking for Stringable objects in non-strict mode
- Propagated $strictTypes from CallableType::accepts() through to CallableTypeHelper - In non-strict mode, Stringable objects are now accepted where string is expected in callable parameters - This fixes false positives for patterns like uasort($stringableArray, 'strnatcasecmp') - In strict_types mode, the stricter behavior is preserved (e.g. closures with explicit string params) - New regression test in tests/PHPStan/Rules/Functions/data/bug-11619.php Closes phpstan/phpstan#11619
1 parent d32efcb commit befcc3e

4 files changed

Lines changed: 42 additions & 5 deletions

File tree

src/Type/CallableType.php

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

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

144144
public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
@@ -147,10 +147,10 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
147147
return $type->isSubTypeOf($this);
148148
}
149149

150-
return $this->isSuperTypeOfInternal($type, false);
150+
return $this->isSuperTypeOfInternal($type, false, true);
151151
}
152152

153-
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult
153+
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny, bool $strictTypes): IsSuperTypeOfResult
154154
{
155155
$isCallable = new IsSuperTypeOfResult($type->isCallable(), []);
156156
if ($isCallable->no()) {
@@ -183,7 +183,7 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSup
183183
if (!$variant instanceof CallableParametersAcceptor) {
184184
return IsSuperTypeOfResult::createNo([]);
185185
}
186-
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny);
186+
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny, $strictTypes);
187187
if ($variantsResult === null) {
188188
$variantsResult = $isSuperType;
189189
} else {

src/Type/CallableTypeHelper.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public static function isParametersAcceptorSuperTypeOf(
1616
CallableParametersAcceptor $ours,
1717
CallableParametersAcceptor $theirs,
1818
bool $treatMixedAsAny,
19+
bool $strictTypes = true,
1920
): IsSuperTypeOfResult
2021
{
2122
$theirParameters = $theirs->getParameters();
@@ -72,7 +73,7 @@ public static function isParametersAcceptorSuperTypeOf(
7273
}
7374

7475
if ($treatMixedAsAny) {
75-
$isSuperType = $theirParameter->getType()->accepts($ourParameterType, true);
76+
$isSuperType = $theirParameter->getType()->accepts($ourParameterType, $strictTypes);
7677
$isSuperType = new IsSuperTypeOfResult($isSuperType->result, $isSuperType->reasons);
7778
} else {
7879
$isSuperType = $theirParameter->getType()->isSuperTypeOf($ourParameterType);

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,4 +2746,10 @@ public function testBug13247(): void
27462746
$this->analyse([__DIR__ . '/data/bug-13247.php'], []);
27472747
}
27482748

2749+
#[RequiresPhp('>= 8.1')]
2750+
public function testBug11619(): void
2751+
{
2752+
$this->analyse([__DIR__ . '/data/bug-11619.php'], []);
2753+
}
2754+
27492755
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug11619;
4+
5+
final class Foo implements \Stringable {
6+
7+
private function __construct(public readonly string $value) {
8+
}
9+
10+
public static function fromString(string $string): self {
11+
return new self($string);
12+
}
13+
14+
public function __toString(): string {
15+
return $this->value;
16+
}
17+
18+
}
19+
20+
function test(): void
21+
{
22+
$options = [
23+
Foo::fromString('c'),
24+
Foo::fromString('b'),
25+
Foo::fromString('a'),
26+
];
27+
28+
uasort($options, 'strnatcasecmp');
29+
usort($options, 'strnatcasecmp');
30+
}

0 commit comments

Comments
 (0)