diff --git a/src/Analyser/Generator/ExprHandler/InterpolatedStringHandler.php b/src/Analyser/Generator/ExprHandler/InterpolatedStringHandler.php new file mode 100644 index 0000000000..0646f66bf2 --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/InterpolatedStringHandler.php @@ -0,0 +1,99 @@ + + */ +#[AutowiredService] +final class InterpolatedStringHandler implements ExprHandler +{ + + public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof InterpolatedString; + } + + public function analyseExpr( + Stmt $stmt, + Expr $expr, + GeneratorScope $scope, + ExpressionContext $context, + ?callable $alternativeNodeCallback, + ): Generator + { + yield from []; + + $resultType = null; + $resultNativeType = null; + + $hasYield = false; + $throwPoints = []; + $impurePoints = []; + $isAlwaysTerminating = false; + foreach ($expr->parts as $part) { + if ($part instanceof InterpolatedStringPart) { + $partType = new ConstantStringType($part->value); + $partNativeType = $partType; + } else { + $result = yield new ExprAnalysisRequest($stmt, $part, $scope, $context->enterDeep(), $alternativeNodeCallback); + $partType = $result->type->toString(); + $partNativeType = $result->nativeType->toString(); + + $hasYield = $hasYield || $result->hasYield; + $throwPoints = array_merge($throwPoints, $result->throwPoints); + $impurePoints = array_merge($impurePoints, $result->impurePoints); + $isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating; + $scope = $result->scope; + } + + if ($resultType === null) { + $resultType = $partType; + $resultNativeType = $partNativeType; + continue; + } + + if ($resultNativeType === null) { + throw new ShouldNotHappenException(); + } + + $resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType); + $resultNativeType = $this->initializerExprTypeResolver->resolveConcatType($resultNativeType, $partNativeType); + } + + return new ExprAnalysisResult( + $resultType ?? new ConstantStringType(''), + $resultNativeType ?? new ConstantStringType(''), + $scope, + hasYield: $hasYield, + isAlwaysTerminating: $isAlwaysTerminating, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + specifiedTruthyTypes: new SpecifiedTypes(), + specifiedFalseyTypes: new SpecifiedTypes(), + specifiedNullTypes: new SpecifiedTypes(), + ); + } + +} diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index a142ab7bed..0cb34eeada 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -247,6 +247,19 @@ function doCast() { assertType("'1'", (string) $a); } + /** + * @param '1' $b + */ + function doInterpolatedString(string $b) { + $a = '1'; + + assertType("'1'", "$a"); + assertNativeType("'1'", "$a"); + assertType("'1'", "$b"); + assertNativeType("string", "$b"); + } + + } function (): void {