Skip to content

Commit

Permalink
Variadic parameters in PHP8 can have int|string key
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural committed Oct 20, 2021
1 parent 2c11075 commit 399eeb5
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 7 deletions.
8 changes: 7 additions & 1 deletion src/Analyser/DirectScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand Down Expand Up @@ -39,6 +40,8 @@ class DirectScopeFactory implements ScopeFactory
/** @var string[] */
private array $dynamicConstantNames;

private PhpVersion $phpVersion;

public function __construct(
string $scopeClass,
ReflectionProvider $reflectionProvider,
Expand All @@ -50,7 +53,8 @@ public function __construct(
\PHPStan\Parser\Parser $parser,
NodeScopeResolver $nodeScopeResolver,
bool $treatPhpDocTypesAsCertain,
Container $container
Container $container,
PhpVersion $phpVersion
)
{
$this->scopeClass = $scopeClass;
Expand All @@ -64,6 +68,7 @@ public function __construct(
$this->nodeScopeResolver = $nodeScopeResolver;
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain;
$this->dynamicConstantNames = $container->getParameter('dynamicConstantNames');
$this->phpVersion = $phpVersion;
}

/**
Expand Down Expand Up @@ -121,6 +126,7 @@ public function create(
$this->parser,
$this->nodeScopeResolver,
$context,
$this->phpVersion,
$declareStrictTypes,
$constantTypes,
$function,
Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/LazyScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand Down Expand Up @@ -89,6 +90,7 @@ public function create(
$this->container->getService('currentPhpVersionSimpleParser'),
$this->container->getByType(NodeScopeResolver::class),
$context,
$this->container->getByType(PhpVersion::class),
$declareStrictTypes,
$constantTypes,
$function,
Expand Down
21 changes: 20 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use PhpParser\NodeFinder;
use PHPStan\Node\ExecutionEndNode;
use PHPStan\Parser\Parser;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ClassConstantReflection;
use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
Expand Down Expand Up @@ -132,6 +133,8 @@ class MutatingScope implements Scope

private \PHPStan\Analyser\ScopeContext $context;

private PhpVersion $phpVersion;

/** @var \PHPStan\Type\Type[] */
private array $resolvedTypes = [];

Expand Down Expand Up @@ -189,6 +192,7 @@ class MutatingScope implements Scope
* @param Parser $parser
* @param NodeScopeResolver $nodeScopeResolver
* @param \PHPStan\Analyser\ScopeContext $context
* @param PhpVersion $phpVersion
* @param bool $declareStrictTypes
* @param array<string, Type> $constantTypes
* @param \PHPStan\Reflection\FunctionReflection|MethodReflection|null $function
Expand Down Expand Up @@ -218,6 +222,7 @@ public function __construct(
Parser $parser,
NodeScopeResolver $nodeScopeResolver,
ScopeContext $context,
PhpVersion $phpVersion,
bool $declareStrictTypes = false,
array $constantTypes = [],
$function = null,
Expand Down Expand Up @@ -251,6 +256,7 @@ public function __construct(
$this->parser = $parser;
$this->nodeScopeResolver = $nodeScopeResolver;
$this->context = $context;
$this->phpVersion = $phpVersion;
$this->declareStrictTypes = $declareStrictTypes;
$this->constantTypes = $constantTypes;
$this->function = $function;
Expand Down Expand Up @@ -2384,6 +2390,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope
$this->parser,
$this->nodeScopeResolver,
$this->context,
$this->phpVersion,
$this->declareStrictTypes,
$this->constantTypes,
$this->function,
Expand Down Expand Up @@ -2954,7 +2961,11 @@ private function enterFunctionLike(
foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) {
$parameterType = $parameter->getType();
if ($parameter->isVariadic()) {
$parameterType = new ArrayType(new IntegerType(), $parameterType);
if ($this->phpVersion->supportsNamedArguments()) {
$parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType);
} else {
$parameterType = new ArrayType(new IntegerType(), $parameterType);
}
}
$variableTypes[$parameter->getName()] = VariableTypeHolder::createYes($parameterType);
$nativeExpressionTypes[sprintf('$%s', $parameter->getName())] = $parameter->getNativeType();
Expand Down Expand Up @@ -3374,6 +3385,14 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type
);
}
if ($isVariadic) {
if ($this->phpVersion->supportsNamedArguments()) {
return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType(
$type,
false,
false
));
}

return new ArrayType(new IntegerType(), $this->getFunctionType(
$type,
false,
Expand Down
4 changes: 3 additions & 1 deletion src/Testing/PHPStanTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
use PHPStan\File\FileHelper;
use PHPStan\Parser\Parser;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDoc\TypeStringResolver;
use PHPStan\Reflection\ReflectionProvider;
Expand Down Expand Up @@ -131,7 +132,8 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS
$this->getParser(),
self::getContainer()->getByType(NodeScopeResolver::class),
$this->shouldTreatPhpDocTypesAsCertain(),
$container
$container,
$container->getByType(PhpVersion::class)
);
}

Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ public function dataParameterTypes(): array
'$callable',
],
[
'array<int, string>',
PHP_VERSION_ID < 80000 ? 'array<int, string>' : 'array<int|string, string>',
'$variadicStrings',
],
[
Expand Down Expand Up @@ -4342,7 +4342,7 @@ public function dataAnonymousFunction(): array
'$str',
],
[
'array<int, mixed>',
PHP_VERSION_ID < 80000 ? 'array<int, mixed>' : 'array<int|string, mixed>',
'$arr',
],
[
Expand Down
16 changes: 14 additions & 2 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/catch-without-variable.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/mixed-typehint.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600.php');
if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600-php8.php');
} else {
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-typehint-without-null-in-phpdoc.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/override-root-scope-variable.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bitwise-not.php');
Expand Down Expand Up @@ -427,7 +431,11 @@ public function dataFileAsserts(): iterable

if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 70400) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/arrow-function-types.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php');
if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902-php8.php');
} else {
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php');
}
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-types.php');
Expand Down Expand Up @@ -508,6 +516,10 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4741.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/more-type-strings.php');

if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/variadic-parameter-php8.php');
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5628.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php');
Expand Down
88 changes: 88 additions & 0 deletions tests/PHPStan/Analyser/data/bug-2600-php8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Bug2600;

use function PHPStan\Testing\assertType;

class Foo
{
/**
* @param mixed ...$x
*/
public function doFoo($x = null) {
$args = func_get_args();
assertType('mixed', $x);
assertType('array', $args);
}

/**
* @param mixed ...$x
*/
public function doBar($x = null) {
assertType('mixed', $x);
}

/**
* @param mixed $x
*/
public function doBaz(...$x) {
assertType('array<int|string, mixed>', $x);
}

/**
* @param mixed ...$x
*/
public function doLorem(...$x) {
assertType('array<int|string, mixed>', $x);
}

public function doIpsum($x = null) {
$args = func_get_args();
assertType('mixed', $x);
assertType('array', $args);
}
}

class Bar
{
/**
* @param string ...$x
*/
public function doFoo($x = null) {
$args = func_get_args();
assertType('string|null', $x);
assertType('array', $args);
}

/**
* @param string ...$x
*/
public function doBar($x = null) {
assertType('string|null', $x);
}

/**
* @param string $x
*/
public function doBaz(...$x) {
assertType('array<int|string, string>', $x);
}

/**
* @param string ...$x
*/
public function doLorem(...$x) {
assertType('array<int|string, string>', $x);
}
}

function foo($x, string ...$y): void
{
assertType('mixed', $x);
assertType('array<int|string, string>', $y);
}

function ($x, string ...$y): void {
assertType('mixed', $x);
assertType('array<int|string, string>', $y);
};
53 changes: 53 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4902-php8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php // lint >= 7.4

namespace Bug4902;

use function PHPStan\Testing\assertType;

/**
* @template T-wrapper
*/
class Wrapper {
/** @var T-wrapper */
public $value;

/**
* @param T-wrapper $value
*/
public function __construct($value) {
$this->value = $value;
}

/**
* @template T-unwrap
* @param Wrapper<T-unwrap> $wrapper
* @return T-unwrap
*/
function unwrap(Wrapper $wrapper) {
return $wrapper->value;
}

/**
* @template T-wrap
* @param T-wrap $value
*
* @return Wrapper<T-wrap>
*/
function wrap($value): Wrapper
{
return new Wrapper($value);
}


/**
* @template T-all
* @param Wrapper<T-all> ...$wrappers
*/
function unwrapAllAndWrapAgain(Wrapper ...$wrappers): void {
assertType('array<int|string, T-all (method Bug4902\Wrapper::unwrapAllAndWrapAgain(), argument)>', array_map(function (Wrapper $item) {
return $this->unwrap($item);
}, $wrappers));
assertType('array<int|string, T-all (method Bug4902\Wrapper::unwrapAllAndWrapAgain(), argument)>', array_map(fn (Wrapper $item) => $this->unwrap($item), $wrappers));
}

}
18 changes: 18 additions & 0 deletions tests/PHPStan/Analyser/data/variadic-parameter-php8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace VariadicParameterPHP8;

use function PHPStan\Testing\assertType;

function foo(...$args)
{
assertType('array<int|string, mixed>', $args);
assertType('mixed', $args['foo']);
assertType('mixed', $args['bar']);
}

function bar(string ...$args)
{
assertType('array<int|string, string>', $args);
}

0 comments on commit 399eeb5

Please sign in to comment.