Skip to content

Commit

Permalink
get_defined_vars() return type contains know variables
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinMystikJonas committed Nov 12, 2024
1 parent 71d01d6 commit e9ee834
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 0 deletions.
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,11 @@ services:
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\GetDefinedVarsFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension
tags:
Expand Down
68 changes: 68 additions & 0 deletions src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use function array_filter;
use function array_map;
use function array_merge;
use function array_values;
use function count;
use function range;

// based on code by @ruudk
final class GetDefinedVarsFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'get_defined_vars';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
if ($scope->canAnyVariableExist()) {
return new ArrayType(
new StringType(),
new MixedType(),
);
}

$variables = array_values(array_filter(
$scope->getDefinedVariables(),
static fn ($variable) => $variable !== 'this',
));

$maybeVariables = array_values(
$scope->getMaybeDefinedVariables(),
);

$keys = array_map(
static fn ($variable) => new ConstantStringType($variable),
array_merge($variables, $maybeVariables),
);

$values = array_map(
static fn ($variable) => $scope->getVariableType($variable),
array_merge($variables, $maybeVariables),
);

if ($maybeVariables !== []) {
$maybeIndexes = range(count($variables), count($variables) + count($maybeVariables));
} else {
$maybeIndexes = [];
}

return new ConstantArrayType($keys, $values, [0], $maybeIndexes);
}

}
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/nsrt/get-defined-vars.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace GetDefinedVars;

use function PHPStan\Testing\assertType;

function doFoo(int $param) {
$local = "foo";
assertType('array{param: int, local: \'foo\'}', get_defined_vars());
assertType('array{\'param\', \'local\'}', array_keys(get_defined_vars()));
}

function doBar(int $param) {
$local = "foo";
if(true) {
$conditional = "bar";
assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars());
} else {
$other = "baz";
assertType('array{param: int, local: \'foo\', other: \'baz\'}', get_defined_vars());
}
assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars());
}

function doBaz(int $param) {
$local = "foo";
if(rand(0, 1)) {
$random1 = "bar";
assertType('array{param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars());
} else {
$random2 = "baz";
assertType('array{param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars());
}
assertType('array{param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars());
}

0 comments on commit e9ee834

Please sign in to comment.