Skip to content

Commit efcc982

Browse files
authored
Normalize arguments before calling into TypeSpecifyingExtensions
1 parent dc8c831 commit efcc982

File tree

3 files changed

+76
-9
lines changed

3 files changed

+76
-9
lines changed

src/Analyser/ArgumentsNormalizer.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ public static function reorderFuncArguments(
9898
return null;
9999
}
100100

101+
// return identical object if not reordered, as TypeSpecifier relies on object identity
102+
if ($reorderedArgs === $functionCall->getArgs()) {
103+
return $functionCall;
104+
}
105+
101106
return new FuncCall(
102107
$functionCall->name,
103108
$reorderedArgs,
@@ -116,6 +121,11 @@ public static function reorderMethodArguments(
116121
return null;
117122
}
118123

124+
// return identical object if not reordered, as TypeSpecifier relies on object identity
125+
if ($reorderedArgs === $methodCall->getArgs()) {
126+
return $methodCall;
127+
}
128+
119129
return new MethodCall(
120130
$methodCall->var,
121131
$methodCall->name,
@@ -135,6 +145,11 @@ public static function reorderStaticCallArguments(
135145
return null;
136146
}
137147

148+
// return identical object if not reordered, as TypeSpecifier relies on object identity
149+
if ($reorderedArgs === $staticCall->getArgs()) {
150+
return $staticCall;
151+
}
152+
138153
return new StaticCall(
139154
$staticCall->class,
140155
$staticCall->name,
@@ -154,6 +169,11 @@ public static function reorderNewArguments(
154169
return null;
155170
}
156171

172+
// return identical object if not reordered, as TypeSpecifier relies on object identity
173+
if ($reorderedArgs === $new->getArgs()) {
174+
return $new;
175+
}
176+
157177
return new New_(
158178
$new->class,
159179
$reorderedArgs,

src/Analyser/TypeSpecifier.php

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,15 @@ public function specifyTypesInCondition(
476476

477477
} elseif ($expr instanceof FuncCall && $expr->name instanceof Name) {
478478
if ($this->reflectionProvider->hasFunction($expr->name, $scope)) {
479+
// lazy create parametersAcceptor, as creation can be expensive
480+
$parametersAcceptor = null;
481+
479482
$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
483+
if (count($expr->getArgs()) > 0) {
484+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $functionReflection->getVariants(), $functionReflection->getNamedArgumentsVariants());
485+
$expr = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr) ?? $expr;
486+
}
487+
480488
foreach ($this->getFunctionTypeSpecifyingExtensions() as $extension) {
481489
if (!$extension->isFunctionSupported($functionReflection, $expr, $context)) {
482490
continue;
@@ -485,10 +493,10 @@ public function specifyTypesInCondition(
485493
return $extension->specifyTypes($functionReflection, $expr, $scope, $context);
486494
}
487495

488-
// lazy create parametersAcceptor, as creation can be expensive
489-
$parametersAcceptor = null;
490496
if (count($expr->getArgs()) > 0) {
491-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $functionReflection->getVariants(), $functionReflection->getNamedArgumentsVariants());
497+
if ($parametersAcceptor === null) {
498+
throw new ShouldNotHappenException();
499+
}
492500

493501
$specifiedTypes = $this->specifyTypesFromConditionalReturnType($context, $expr, $parametersAcceptor, $scope);
494502
if ($specifiedTypes !== null) {
@@ -518,6 +526,14 @@ public function specifyTypesInCondition(
518526
$methodCalledOnType = $scope->getType($expr->var);
519527
$methodReflection = $scope->getMethodReflection($methodCalledOnType, $expr->name->name);
520528
if ($methodReflection !== null) {
529+
// lazy create parametersAcceptor, as creation can be expensive
530+
$parametersAcceptor = null;
531+
532+
if (count($expr->getArgs()) > 0) {
533+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $methodReflection->getVariants(), $methodReflection->getNamedArgumentsVariants());
534+
$expr = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $expr) ?? $expr;
535+
}
536+
521537
$referencedClasses = $methodCalledOnType->getObjectClassNames();
522538
if (
523539
count($referencedClasses) === 1
@@ -533,10 +549,10 @@ public function specifyTypesInCondition(
533549
}
534550
}
535551

536-
// lazy create parametersAcceptor, as creation can be expensive
537-
$parametersAcceptor = null;
538552
if (count($expr->getArgs()) > 0) {
539-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $methodReflection->getVariants(), $methodReflection->getNamedArgumentsVariants());
553+
if ($parametersAcceptor === null) {
554+
throw new ShouldNotHappenException();
555+
}
540556

541557
$specifiedTypes = $this->specifyTypesFromConditionalReturnType($context, $expr, $parametersAcceptor, $scope);
542558
if ($specifiedTypes !== null) {
@@ -571,6 +587,14 @@ public function specifyTypesInCondition(
571587

572588
$staticMethodReflection = $scope->getMethodReflection($calleeType, $expr->name->name);
573589
if ($staticMethodReflection !== null) {
590+
// lazy create parametersAcceptor, as creation can be expensive
591+
$parametersAcceptor = null;
592+
593+
if (count($expr->getArgs()) > 0) {
594+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $staticMethodReflection->getVariants(), $staticMethodReflection->getNamedArgumentsVariants());
595+
$expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr;
596+
}
597+
574598
$referencedClasses = $calleeType->getObjectClassNames();
575599
if (
576600
count($referencedClasses) === 1
@@ -586,10 +610,10 @@ public function specifyTypesInCondition(
586610
}
587611
}
588612

589-
// lazy create parametersAcceptor, as creation can be expensive
590-
$parametersAcceptor = null;
591613
if (count($expr->getArgs()) > 0) {
592-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $staticMethodReflection->getVariants(), $staticMethodReflection->getNamedArgumentsVariants());
614+
if ($parametersAcceptor === null) {
615+
throw new ShouldNotHappenException();
616+
}
593617

594618
$specifiedTypes = $this->specifyTypesFromConditionalReturnType($context, $expr, $parametersAcceptor, $scope);
595619
if ($specifiedTypes !== null) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug13088;
4+
5+
use function PHPStan\dumpType;
6+
use function PHPStan\Testing\assertType;
7+
8+
class HelloWorld
9+
{
10+
public function sayHello(string $s, int $offset): void
11+
{
12+
if (preg_match('~msgstr "(.*)"\n~', $s, $matches, 0, $offset) === 1) {
13+
assertType('array{non-falsy-string, string}', $matches);
14+
}
15+
}
16+
17+
public function sayHello2(string $s, int $offset): void
18+
{
19+
if (preg_match('~msgstr "(.*)"\n~', $s, $matches, offset: $offset) === 1) {
20+
assertType('array{non-falsy-string, string}', $matches);
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)