Skip to content

Commit

Permalink
Fix variadic parameters by not making them always ArrayType
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 10, 2020
1 parent 4ba83e8 commit a599eaa
Show file tree
Hide file tree
Showing 24 changed files with 271 additions and 118 deletions.
8 changes: 6 additions & 2 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2289,7 +2289,7 @@ private function getRealParameterTypes(Node\FunctionLike $functionLike): array
$realParameterTypes[$parameter->var->name] = $this->getFunctionType(
$parameter->type,
$this->isParameterValueNullable($parameter),
$parameter->variadic
false
);
}

Expand Down Expand Up @@ -2368,7 +2368,11 @@ private function enterFunctionLike(
$variableTypes = $this->getVariableTypes();
$nativeExpressionTypes = [];
foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) {
$variableTypes[$parameter->getName()] = VariableTypeHolder::createYes($parameter->getType());
$parameterType = $parameter->getType();
if ($parameter->isVariadic()) {
$parameterType = new ArrayType(new IntegerType(), $parameterType);
}
$variableTypes[$parameter->getName()] = VariableTypeHolder::createYes($parameterType);
$nativeExpressionTypes[sprintf('$%s', $parameter->getName())] = $parameter->getNativeType();
}

Expand Down
11 changes: 0 additions & 11 deletions src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -290,15 +288,6 @@ public function resolveParamTags(PhpDocNode $phpDocNode, NameScope $nameScope):
continue;
}

if ($tagValue->isVariadic) {
if (!$parameterType instanceof ArrayType) {
$parameterType = new ArrayType(new IntegerType(), $parameterType);

} elseif ($parameterType->getKeyType() instanceof MixedType) {
$parameterType = new ArrayType(new IntegerType(), $parameterType->getItemType());
}
}

$resolved[$parameterName] = new ParamTag(
$parameterType,
$tagValue->isVariadic
Expand Down
9 changes: 1 addition & 8 deletions src/Reflection/GenericParametersAcceptorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
namespace PHPStan\Reflection;

use PHPStan\Reflection\Generic\ResolvedFunctionVariant;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class GenericParametersAcceptorResolver
{
Expand All @@ -24,11 +21,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc

foreach ($parametersAcceptor->getParameters() as $i => $param) {
if (isset($argTypes[$i])) {
if ($param->isVariadic()) {
$argType = new ArrayType(new MixedType(), TypeCombinator::union(...array_slice($argTypes, $i)));
} else {
$argType = $argTypes[$i];
}
$argType = $argTypes[$i];
} elseif ($param->getDefaultValue() !== null) {
$argType = $param->getDefaultValue();
} else {
Expand Down
15 changes: 2 additions & 13 deletions src/Reflection/Native/NativeParameterReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\Type;

class NativeParameterReflection implements ParameterReflection
Expand All @@ -23,16 +21,13 @@ class NativeParameterReflection implements ParameterReflection

private ?\PHPStan\Type\Type $defaultValue;

private bool $variadicParameterAlreadyExpanded;

public function __construct(
string $name,
bool $optional,
Type $type,
PassedByReference $passedByReference,
bool $variadic,
?Type $defaultValue,
bool $variadicParameterAlreadyExpanded = false
?Type $defaultValue
)
{
$this->name = $name;
Expand All @@ -41,7 +36,6 @@ public function __construct(
$this->passedByReference = $passedByReference;
$this->variadic = $variadic;
$this->defaultValue = $defaultValue;
$this->variadicParameterAlreadyExpanded = $variadicParameterAlreadyExpanded;
}

public function getName(): string
Expand All @@ -56,12 +50,7 @@ public function isOptional(): bool

public function getType(): Type
{
$type = $this->type;
if ($this->variadic && !$this->variadicParameterAlreadyExpanded) {
$type = new ArrayType(new IntegerType(), $type);
}

return $type;
return $this->type;
}

public function passedByReference(): PassedByReference
Expand Down
15 changes: 2 additions & 13 deletions src/Reflection/Native/NativeParameterWithPhpDocsReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\Type;

class NativeParameterWithPhpDocsReflection implements ParameterReflectionWithPhpDocs
Expand All @@ -27,8 +25,6 @@ class NativeParameterWithPhpDocsReflection implements ParameterReflectionWithPhp

private ?\PHPStan\Type\Type $defaultValue;

private bool $variadicParameterAlreadyExpanded;

public function __construct(
string $name,
bool $optional,
Expand All @@ -37,8 +33,7 @@ public function __construct(
Type $nativeType,
PassedByReference $passedByReference,
bool $variadic,
?Type $defaultValue,
bool $variadicParameterAlreadyExpanded = false
?Type $defaultValue
)
{
$this->name = $name;
Expand All @@ -49,7 +44,6 @@ public function __construct(
$this->passedByReference = $passedByReference;
$this->variadic = $variadic;
$this->defaultValue = $defaultValue;
$this->variadicParameterAlreadyExpanded = $variadicParameterAlreadyExpanded;
}

public function getName(): string
Expand All @@ -64,12 +58,7 @@ public function isOptional(): bool

public function getType(): Type
{
$type = $this->type;
if ($this->variadic && !$this->variadicParameterAlreadyExpanded) {
$type = new ArrayType(new IntegerType(), $type);
}

return $type;
return $this->type;
}

public function getPhpDocType(): Type
Expand Down
6 changes: 2 additions & 4 deletions src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,7 @@ private function createNativeMethodVariant(
$nativeReturnType = TypehintHelper::decideTypeFromReflection(
$reflectionMethod->getReturnType(),
null,
null,
false
null
);
}
foreach ($methodSignature->getParameters() as $i => $parameterSignature) {
Expand All @@ -606,8 +605,7 @@ private function createNativeMethodVariant(
$nativeParameterType,
$parameterSignature->passedByReference(),
$stubPhpDocParameterVariadicity[$parameterSignature->getName()] ?? $parameterSignature->isVariadic(),
null,
isset($stubPhpDocParameterTypes[$parameterSignature->getName()])
null
);
}

Expand Down
35 changes: 4 additions & 31 deletions src/Rules/FunctionCallParametersCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
Expand Down Expand Up @@ -149,44 +146,20 @@ static function (Type $type): bool {
}

$parameter = $parameters[count($parameters) - 1];
$parameterType = $parameter->getType();
if (!($parameterType instanceof ArrayType)) {
break;
}

if (!$argument->unpack) {
$parameterType = $parameterType->getItemType();
}
} else {
$parameter = $parameters[$i];
$parameterType = $parameter->getType();
if ($parameter->isVariadic()) {
if ($parameterType instanceof ArrayType && !$argument->unpack) {
$parameterType = $parameterType->getItemType();
}
} elseif ($argument->unpack) {
continue;
}
}

$parameterType = $parameter->getType();

$argumentValueType = $scope->getType($argument->value);
$secondAccepts = null;
if ($parameterType->isIterable()->yes() && $parameter->isVariadic()) {
$secondAccepts = $this->ruleLevelHelper->accepts(
new IterableType(
new MixedType(),
$parameterType->getIterableValueType()
),
$argumentValueType,
$scope->isDeclareStrictTypes()
);
if ($argument->unpack) {
$argumentValueType = $argumentValueType->getIterableValueType();
}

if (
$this->checkArgumentTypes
&& !$parameter->passedByReference()->createsNewVariable()
&& !$this->ruleLevelHelper->accepts($parameterType, $argumentValueType, $scope->isDeclareStrictTypes())
&& ($secondAccepts === null || !$secondAccepts)
) {
$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType);
$errors[] = RuleErrorBuilder::message(sprintf(
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/FunctionDefinitionCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public function checkAnonymousFunction(
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}
$type = $scope->getFunctionType($param->type, false, $param->variadic);
$type = $scope->getFunctionType($param->type, false, false);
if ($type instanceof VoidType) {
$errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, 'void'))->line($param->type->getLine())->nonIgnorable()->build();
}
Expand Down
17 changes: 8 additions & 9 deletions src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ public function processNode(Node $node, Scope $scope): array

} else {
$nativeParamType = $nativeParameterTypes[$parameterName];
if (
$phpDocParamTag->isVariadic()
&& $phpDocParamType instanceof ArrayType
&& !$nativeParamType instanceof ArrayType
) {
$phpDocParamType = $phpDocParamType->getItemType();
}
$isParamSuperType = $nativeParamType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocParamType));

$errors = array_merge($errors, $this->genericObjectTypeCheck->check(
Expand All @@ -106,14 +113,6 @@ public function processNode(Node $node, Scope $scope): array
)
));

if (
$phpDocParamTag->isVariadic()
&& $nativeParamType instanceof ArrayType
&& $nativeParamType->getItemType() instanceof ArrayType
) {
continue;
}

if ($isParamSuperType->no()) {
$errors[] = RuleErrorBuilder::message(sprintf(
'PHPDoc tag @param for parameter $%s with type %s is incompatible with native type %s.',
Expand Down Expand Up @@ -187,7 +186,7 @@ private function getNativeParameterTypes(\PhpParser\Node\FunctionLike $node, Sco
$nativeParameterTypes[$parameter->var->name] = $scope->getFunctionType(
$parameter->type,
$isNullable,
$parameter->variadic
false
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/CallableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function () use ($level): string {
'callable(%s): %s',
implode(', ', array_map(
static function (NativeParameterReflection $param) use ($level): string {
return $param->getType()->describe($level);
return sprintf('%s%s', $param->isVariadic() ? '...' : '', $param->getType()->describe($level));
},
$this->getParameters()
)),
Expand Down
2 changes: 1 addition & 1 deletion src/Type/ClosureType.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function describe(VerbosityLevel $level): string
return sprintf(
'Closure(%s): %s',
implode(', ', array_map(static function (ParameterReflection $parameter) use ($level): string {
return $parameter->getType()->describe($level);
return sprintf('%s%s', $parameter->isVariadic() ? '...' : '', $parameter->getType()->describe($level));
}, $this->parameters)),
$this->returnType->describe($level)
);
Expand Down
13 changes: 12 additions & 1 deletion src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\StringType;
Expand Down Expand Up @@ -35,8 +37,17 @@ public function getTypeFromFunctionCall(
$values[] = $argType->getValue();
}

if (count($values) === 0) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}

$format = array_shift($values);
if (!is_string($format)) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}

try {
$value = @sprintf(...$values);
$value = @sprintf($format, ...$values);
} catch (\Throwable $e) {
return $returnType;
}
Expand Down
7 changes: 3 additions & 4 deletions src/Type/TypehintHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public static function decideTypeFromReflection(
): Type
{
if ($reflectionType === null) {
if ($isVariadic && $phpDocType instanceof ArrayType) {
$phpDocType = $phpDocType->getItemType();
}
return $phpDocType ?? new MixedType();
}

Expand All @@ -78,10 +81,6 @@ public static function decideTypeFromReflection(
$phpDocType = TypeCombinator::removeNull($phpDocType);
}

if ($isVariadic) {
$type = new ArrayType(new IntegerType(), $type);
}

return self::decideType($type, $phpDocType);
}

Expand Down
Loading

0 comments on commit a599eaa

Please sign in to comment.