Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/Analyser/Generator/ExprHandler/InterpolatedStringHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Generator\ExprHandler;

use Generator;
use PhpParser\Node\Expr;
use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
use PHPStan\Analyser\Generator\ExprAnalysisResult;
use PHPStan\Analyser\Generator\ExprHandler;
use PHPStan\Analyser\Generator\GeneratorScope;
use PHPStan\Analyser\Generator\NoopNodeCallback;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Type\Constant\ConstantStringType;
use function array_merge;

/**
* @implements ExprHandler<InterpolatedString>
*/
#[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;
$hasYield = false;
$throwPoints = [];
$impurePoints = [];
$isAlwaysTerminating = false;
foreach ($expr->parts as $part) {
if ($part instanceof InterpolatedStringPart) {
$partType = new ConstantStringType($part->value);
} else {
$result = yield new ExprAnalysisRequest($stmt, $part, $scope, $context->enterDeep(), new NoopNodeCallback());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is noop-correct here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. It'd mean that for $part no rule is executed. NoopNodeCallback is the equivalent of static function () {} when calling ->processExprNode or ->processStmtNode in NodeScopeResolver.

So it's used when we want a result of a synthetic node (where we do new SomeAstNode(...) because we're interested in the result but otherwise the node is not in the original source so it would not make sense to report errors on it).

Also we'd use NoopNodeCallback when we need ExprAnalysisRequest for something that we're sure has already been analysed. Thanks to

if ($alternativeNodeCallback instanceof NoopNodeCallback) {
return $foundExprAnalysisResult;
}
it'd just return the result instead of throwing an exception.

$partType = $result->type->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;
continue;
}

$resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType);
}

$type = $resultType ?? new ConstantStringType('');
return new ExprAnalysisResult(
$type,
$type,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about native type here? :)

$scope,
hasYield: $hasYield,
isAlwaysTerminating: $isAlwaysTerminating,
throwPoints: $throwPoints,
impurePoints: $impurePoints,
specifiedTruthyTypes: new SpecifiedTypes(),
specifiedFalseyTypes: new SpecifiedTypes(),
specifiedNullTypes: new SpecifiedTypes(),
);
}

}
8 changes: 8 additions & 0 deletions tests/PHPStan/Analyser/Generator/data/gnsr.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ function doCast() {
assertType("'1'", (string) $a);
}

function doInterpolatedString() {
$a = '1';

assertType("'1'", "$a");
assertNativeType("'1'", "$a");
}


}

function (): void {
Expand Down
Loading