Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion src/Turbo/TurboExtensionEnabler.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
final class TurboExtensionEnabler
{

public const EXPECTED_EXTENSION_VERSION = '56d1522';
public const EXPECTED_EXTENSION_VERSION = '827a46f';

private static bool $active = false;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\StmtHandler;

use PhpParser\Node\Expr;
use PHPStan\Analyser\ConditionalExpressionHolder;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Type\Type;
use function count;

/**
* A MutatingScope subclass overriding addConditionalExpressions(), so the
* native engine has to hand the holder tables over through a PHP call.
* enterForeach() and mergeWith() answer $this to keep this subclass the
* scope ForeachHandler narrows.
*/
final class ConditionalExpressionsRecordingScope extends MutatingScope
{

/** @var list<array{string, int}> */
public array $added = [];

public function enterForeach(MutatingScope $originalScope, Expr $iteratee, Type $iterateeType, Type $nativeIterateeType, string $valueName, ?string $keyName, bool $valueByRef): MutatingScope
{
return $this;
}

public function mergeWith(?MutatingScope $otherScope, bool $preserveVacuousConditionals = false): MutatingScope
{
return $this;
}

/**
* @param ConditionalExpressionHolder[] $conditionalExpressionHolders
*/
public function addConditionalExpressions(string $exprString, array $conditionalExpressionHolders): MutatingScope
{
$this->added[] = [$exprString, count($conditionalExpressionHolders)];

return $this;
}

}
62 changes: 62 additions & 0 deletions tests/PHPStan/Analyser/StmtHandler/ForeachHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\StmtHandler;

use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Foreach_;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\NoopNodeCallback;
use PHPStan\Analyser\ScopeContext;
use PHPStan\Analyser\StatementContext;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPUnit\Framework\Attributes\DataProvider;
use ReflectionClass;

class ForeachHandlerTest extends PHPStanTestCase
{

public static function dataConstantArrayConditionalHolders(): iterable
{
// no keys: both holder tables stay the empty array literal
yield [new ConstantArrayType([], []), 0];
yield [new ConstantArrayType([new ConstantStringType('a')], [new ConstantIntegerType(1)]), 1];
}

#[DataProvider('dataConstantArrayConditionalHolders')]
public function testConstantArrayConditionalHoldersReachScopeOverride(ConstantArrayType $iterateeType, int $expectedHolders): void
{
$container = self::getContainer();
$scope = self::createScopeFactory(self::createReflectionProvider(), $container->getService('typeSpecifier'))
->create(ScopeContext::create(__FILE__))
->assignVariable('a', $iterateeType, $iterateeType, TrinaryLogic::createYes());
// the scope factory only creates MutatingScope: rebuild the scope as
// the subclass from its constructor arguments
$args = [];
$reflection = new ReflectionClass(MutatingScope::class);
foreach ($reflection->getMethod('__construct')->getParameters() as $parameter) {
$args[$parameter->getName()] = $reflection->getProperty($parameter->getName())->getValue($scope);
}
$scope = new ConditionalExpressionsRecordingScope(...$args);

$container->getByType(ForeachHandler::class)->processStmt(
$container->getByType(NodeScopeResolver::class),
new Foreach_(new Variable('a'), new Variable('v'), ['keyVar' => new Variable('k')]),
$scope,
new ExpressionResultStorage(),
new NoopNodeCallback(),
StatementContext::createDeep(),
);

$this->assertSame([
['$v', $expectedHolders],
['$a[$k]', $expectedHolders],
], $scope->added);
}

}
10 changes: 9 additions & 1 deletion turbo-ext/src/zv.h
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,15 @@ class Args
static zend_always_inline void set(zval *slot, const zval *value) { ZVAL_COPY_VALUE(slot, value); }
static zend_always_inline void set(zval *slot, zend_object *value) { ZVAL_OBJ(slot, value); }
static zend_always_inline void set(zval *slot, zend_string *value) { ZVAL_STR(slot, value); }
static zend_always_inline void set(zval *slot, HashTable *value) { ZVAL_ARR(slot, value); }
/* immutable tables (a PHP [] literal) are wrapped non-refcounted, like
* ZVAL_EMPTY_ARRAY: the call's addref of its arguments must not touch them */
static zend_always_inline void set(zval *slot, HashTable *value)
{
ZVAL_ARR(slot, value);
if (GC_FLAGS(value) & IS_ARRAY_IMMUTABLE) {
Z_TYPE_INFO_P(slot) = IS_ARRAY;
}
}
static zend_always_inline void set(zval *slot, bool value) { ZVAL_BOOL(slot, value); }
static zend_always_inline void set(zval *slot, zend_long value) { ZVAL_LONG(slot, value); }
static zend_always_inline void set(zval *slot, double value) { ZVAL_DOUBLE(slot, value); }
Expand Down
Loading