Skip to content

Commit 6938ec5

Browse files
ondrejmirtesstaabm
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 d3a9a3d commit 6938ec5

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

src/Type/CallableType.php

Lines changed: 4 additions & 4 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
@@ -148,10 +148,10 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
148148
return $type->isSubTypeOf($this);
149149
}
150150

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

154-
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult
154+
private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny, bool $strictTypes): IsSuperTypeOfResult
155155
{
156156
$isCallable = new IsSuperTypeOfResult($type->isCallable(), []);
157157
if ($isCallable->no()) {
@@ -184,7 +184,7 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSup
184184
if (!$variant instanceof CallableParametersAcceptor) {
185185
return IsSuperTypeOfResult::createNo([]);
186186
}
187-
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny);
187+
$isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny, $strictTypes);
188188
if ($variantsResult === null) {
189189
$variantsResult = $isSuperType;
190190
} 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
@@ -2741,6 +2741,12 @@ public function testBug12363(): void
27412741
$this->analyse([__DIR__ . '/data/bug-12363.php'], []);
27422742
}
27432743

2744+
#[RequiresPhp('>= 8.1')]
2745+
public function testBug11619(): void
2746+
{
2747+
$this->analyse([__DIR__ . '/data/bug-11619.php'], []);
2748+
}
2749+
27442750
public function testBug13247(): void
27452751
{
27462752
$this->analyse([__DIR__ . '/data/bug-13247.php'], []);
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)