Skip to content

Commit 988666d

Browse files
Improve implode signature
1 parent 1f150cc commit 988666d

File tree

4 files changed

+144
-2
lines changed

4 files changed

+144
-2
lines changed

src/Parser/ImplodeArgVisitor.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use Override;
6+
use PhpParser\Node;
7+
use PhpParser\NodeVisitorAbstract;
8+
use PHPStan\DependencyInjection\AutowiredService;
9+
use function in_array;
10+
11+
#[AutowiredService]
12+
final class ImplodeArgVisitor extends NodeVisitorAbstract
13+
{
14+
15+
public const ATTRIBUTE_NAME = 'isImplodeArg';
16+
17+
#[Override]
18+
public function enterNode(Node $node): ?Node
19+
{
20+
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) {
21+
$functionName = $node->name->toLowerString();
22+
if (in_array($functionName, ['implode', 'join'], true)) {
23+
$args = $node->getRawArgs();
24+
if (isset($args[0])) {
25+
$args[0]->setAttribute(self::ATTRIBUTE_NAME, true);
26+
}
27+
}
28+
}
29+
return null;
30+
}
31+
32+
}

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHPStan\Parser\ClosureBindArgVisitor;
1616
use PHPStan\Parser\ClosureBindToVarVisitor;
1717
use PHPStan\Parser\CurlSetOptArgVisitor;
18+
use PHPStan\Parser\ImplodeArgVisitor;
1819
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
1920
use PHPStan\Reflection\Native\NativeParameterReflection;
2021
use PHPStan\Reflection\Php\DummyParameter;
@@ -203,6 +204,31 @@ public static function selectFromArgs(
203204
];
204205
}
205206

207+
if (isset($args[0]) && (bool) $args[0]->getAttribute(ImplodeArgVisitor::ATTRIBUTE_NAME)) {
208+
if (isset($args[1])) {
209+
$parameters = [
210+
new NativeParameterReflection('separator', false, new StringType(), PassedByReference::createNo(), false, null),
211+
new NativeParameterReflection('array', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null),
212+
];
213+
} else {
214+
$parameters = [
215+
new NativeParameterReflection('array', false, new ArrayType(new MixedType(), new MixedType()), PassedByReference::createNo(), false, null),
216+
];
217+
}
218+
219+
$acceptor = $parametersAcceptors[0];
220+
$parametersAcceptors = [
221+
new FunctionVariant(
222+
$acceptor->getTemplateTypeMap(),
223+
$acceptor->getResolvedTemplateTypeMap(),
224+
$parameters,
225+
$acceptor->isVariadic(),
226+
$acceptor->getReturnType(),
227+
$acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
228+
),
229+
];
230+
}
231+
206232
if (isset($args[0]) && (bool) $args[0]->getAttribute(ArrayWalkArgVisitor::ATTRIBUTE_NAME)) {
207233
$arrayWalkParameters = [
208234
new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), false, PassedByReference::createReadsArgument(), false, null),

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,11 @@ public function testImplodeOnPhp74(): void
322322
if (PHP_VERSION_ID >= 80000) {
323323
$errors = [
324324
[
325-
'Parameter #2 $array of function implode expects array|null, string given.',
325+
'Parameter #1 $separator of function implode expects string, array given.',
326+
8,
327+
],
328+
[
329+
'Parameter #2 $array of function implode expects array, string given.',
326330
8,
327331
],
328332
];
@@ -336,7 +340,11 @@ public function testImplodeOnLessThanPhp74(): void
336340
if (PHP_VERSION_ID >= 80000) {
337341
$errors = [
338342
[
339-
'Parameter #2 $array of function implode expects array|null, string given.',
343+
'Parameter #1 $separator of function implode expects string, array given.',
344+
8,
345+
],
346+
[
347+
'Parameter #2 $array of function implode expects array, string given.',
340348
8,
341349
],
342350
];
@@ -2192,6 +2200,46 @@ public function testBug13065(): void
21922200
$this->analyse([__DIR__ . '/data/bug-13065.php'], $errors);
21932201
}
21942202

2203+
public function testBug5760(): void
2204+
{
2205+
$this->checkExplicitMixed = true;
2206+
$this->checkImplicitMixed = true;
2207+
$this->analyse([__DIR__ . '/data/bug-5760.php'], [
2208+
[
2209+
'Parameter #2 $pieces of function join expects array, list<int>|null given.',
2210+
10,
2211+
],
2212+
[
2213+
'Parameter #1 $glue of function join expects array, list<int>|null given.',
2214+
11,
2215+
],
2216+
[
2217+
'Parameter #2 $array of function implode expects array, list<int>|null given.',
2218+
13,
2219+
],
2220+
[
2221+
'Parameter #1 $separator of function implode expects array, list<int>|null given.',
2222+
14,
2223+
],
2224+
[
2225+
'Parameter #2 $pieces of function join expects array, array<string>|string given.',
2226+
22,
2227+
],
2228+
[
2229+
'Parameter #1 $glue of function join expects array, array<string>|string given.',
2230+
23,
2231+
],
2232+
[
2233+
'Parameter #2 $array of function implode expects array, array<string>|string given.',
2234+
25,
2235+
],
2236+
[
2237+
'Parameter #1 $separator of function implode expects array, array<string>|string given.',
2238+
26,
2239+
],
2240+
]);
2241+
}
2242+
21952243
#[RequiresPhp('>= 8.0')]
21962244
public function testBug12317(): void
21972245
{
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug5760;
4+
5+
/**
6+
* @param list<int>|null $arrayOrNull
7+
*/
8+
function doImplode(?array $arrayOrNull): void
9+
{
10+
join(',', $arrayOrNull);
11+
join($arrayOrNull);
12+
13+
implode(',', $arrayOrNull);
14+
implode($arrayOrNull);
15+
}
16+
17+
/**
18+
* @param array<string>|string $union
19+
*/
20+
function more(array|string $union): void
21+
{
22+
join(',', $union);
23+
join($union);
24+
25+
implode(',', $union);
26+
implode($union);
27+
}
28+
29+
function success(): void
30+
{
31+
join(',', ['']);
32+
join(['']);
33+
34+
implode(',', ['']);
35+
implode(['']);
36+
}

0 commit comments

Comments
 (0)