Skip to content

Commit 0feb183

Browse files
authored
Implement InterpolatedStringHandler
1 parent d8fdb61 commit 0feb183

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\ExprHandler;
4+
5+
use Generator;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\InterpolatedStringPart;
8+
use PhpParser\Node\Scalar\InterpolatedString;
9+
use PhpParser\Node\Stmt;
10+
use PHPStan\Analyser\ExpressionContext;
11+
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
12+
use PHPStan\Analyser\Generator\ExprAnalysisResult;
13+
use PHPStan\Analyser\Generator\ExprHandler;
14+
use PHPStan\Analyser\Generator\GeneratorScope;
15+
use PHPStan\Analyser\SpecifiedTypes;
16+
use PHPStan\DependencyInjection\AutowiredService;
17+
use PHPStan\Reflection\InitializerExprTypeResolver;
18+
use PHPStan\ShouldNotHappenException;
19+
use PHPStan\Type\Constant\ConstantStringType;
20+
use function array_merge;
21+
22+
/**
23+
* @implements ExprHandler<InterpolatedString>
24+
*/
25+
#[AutowiredService]
26+
final class InterpolatedStringHandler implements ExprHandler
27+
{
28+
29+
public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver)
30+
{
31+
}
32+
33+
public function supports(Expr $expr): bool
34+
{
35+
return $expr instanceof InterpolatedString;
36+
}
37+
38+
public function analyseExpr(
39+
Stmt $stmt,
40+
Expr $expr,
41+
GeneratorScope $scope,
42+
ExpressionContext $context,
43+
?callable $alternativeNodeCallback,
44+
): Generator
45+
{
46+
yield from [];
47+
48+
$resultType = null;
49+
$resultNativeType = null;
50+
51+
$hasYield = false;
52+
$throwPoints = [];
53+
$impurePoints = [];
54+
$isAlwaysTerminating = false;
55+
foreach ($expr->parts as $part) {
56+
if ($part instanceof InterpolatedStringPart) {
57+
$partType = new ConstantStringType($part->value);
58+
$partNativeType = $partType;
59+
} else {
60+
$result = yield new ExprAnalysisRequest($stmt, $part, $scope, $context->enterDeep(), $alternativeNodeCallback);
61+
$partType = $result->type->toString();
62+
$partNativeType = $result->nativeType->toString();
63+
64+
$hasYield = $hasYield || $result->hasYield;
65+
$throwPoints = array_merge($throwPoints, $result->throwPoints);
66+
$impurePoints = array_merge($impurePoints, $result->impurePoints);
67+
$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating;
68+
$scope = $result->scope;
69+
}
70+
71+
if ($resultType === null) {
72+
$resultType = $partType;
73+
$resultNativeType = $partNativeType;
74+
continue;
75+
}
76+
77+
if ($resultNativeType === null) {
78+
throw new ShouldNotHappenException();
79+
}
80+
81+
$resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType);
82+
$resultNativeType = $this->initializerExprTypeResolver->resolveConcatType($resultNativeType, $partNativeType);
83+
}
84+
85+
return new ExprAnalysisResult(
86+
$resultType ?? new ConstantStringType(''),
87+
$resultNativeType ?? new ConstantStringType(''),
88+
$scope,
89+
hasYield: $hasYield,
90+
isAlwaysTerminating: $isAlwaysTerminating,
91+
throwPoints: $throwPoints,
92+
impurePoints: $impurePoints,
93+
specifiedTruthyTypes: new SpecifiedTypes(),
94+
specifiedFalseyTypes: new SpecifiedTypes(),
95+
specifiedNullTypes: new SpecifiedTypes(),
96+
);
97+
}
98+
99+
}

tests/PHPStan/Analyser/Generator/data/gnsr.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,19 @@ function doCast() {
271271
assertType("'1'", (string) $a);
272272
}
273273

274+
/**
275+
* @param '1' $b
276+
*/
277+
function doInterpolatedString(string $b) {
278+
$a = '1';
279+
280+
assertType("'1'", "$a");
281+
assertNativeType("'1'", "$a");
282+
assertType("'1'", "$b");
283+
assertNativeType("string", "$b");
284+
}
285+
286+
274287
}
275288

276289
function (): void {

0 commit comments

Comments
 (0)