Skip to content
Merged
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/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ public function getKeepVoidType(Expr $node): Type
return $this->getType($clonedNode);
}

public function doNotTreatPhpDocTypesAsCertain(): Scope
public function doNotTreatPhpDocTypesAsCertain(): self
{
return $this->promoteNativeTypes();
}
Expand Down
16 changes: 16 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3691,6 +3691,14 @@ public function processArgs(

if ($overwritingParameterType !== null) {
$parameterType = $overwritingParameterType;

// resolve the native flavour through the same extension on the
// natively-promoted scope, so the closure parameters keep
// their native precision too
$overwritingParameterNativeType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass->doNotTreatPhpDocTypesAsCertain());
if ($overwritingParameterNativeType !== null) {
$parameterNativeType = $overwritingParameterNativeType;
}
}
}

Expand Down Expand Up @@ -3766,6 +3774,14 @@ public function processArgs(

if ($overwritingParameterType !== null) {
$parameterType = $overwritingParameterType;

// resolve the native flavour through the same extension on the
// natively-promoted scope, so the closure parameters keep
// their native precision too
$overwritingParameterNativeType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass->doNotTreatPhpDocTypesAsCertain());
if ($overwritingParameterNativeType !== null) {
$parameterNativeType = $overwritingParameterNativeType;
}
}
}

Expand Down
62 changes: 62 additions & 0 deletions src/Type/Php/ArrayFilterParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\BooleanType;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\FunctionParameterClosureTypeExtension;
use PHPStan\Type\Type;
use const ARRAY_FILTER_USE_BOTH;
use const ARRAY_FILTER_USE_KEY;

#[AutowiredService]
final class ArrayFilterParameterClosureTypeExtension implements FunctionParameterClosureTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
{
return $functionReflection->getName() === 'array_filter' && $parameter->getName() === 'callback';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
if (!isset($args[0])) {
return null;
}

$arrayType = $scope->getType($args[0]->value);
$parameters = null;
if (isset($args[2])) {
$mode = $scope->getType($args[2]->value);
if ($mode instanceof ConstantIntegerType) {
if ($mode->getValue() === ARRAY_FILTER_USE_KEY) {
$parameters = [$this->createParameter('key', $scope->getIterableKeyType($arrayType))];
} elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) {
$parameters = [
$this->createParameter('item', $scope->getIterableValueType($arrayType)),
$this->createParameter('key', $scope->getIterableKeyType($arrayType)),
];
}
}
}

$parameters ??= [$this->createParameter('item', $scope->getIterableValueType($arrayType))];

return new ClosureType($parameters, new BooleanType());
}

private function createParameter(string $name, Type $type): NativeParameterReflection
{
return new NativeParameterReflection($name, false, $type, PassedByReference::createNo(), false, null);
}

}
43 changes: 43 additions & 0 deletions src/Type/Php/ArrayFindParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\BooleanType;
use PHPStan\Type\ClosureType;
use PHPStan\Type\FunctionParameterClosureTypeExtension;
use PHPStan\Type\Type;
use function in_array;

#[AutowiredService]
final class ArrayFindParameterClosureTypeExtension implements FunctionParameterClosureTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
{
return in_array($functionReflection->getName(), ['array_find', 'array_find_key', 'array_any', 'array_all'], true)
&& $parameter->getName() === 'callback';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
if (!isset($args[0])) {
return null;
}

$arrayType = $scope->getType($args[0]->value);

return new ClosureType([
new NativeParameterReflection('value', false, $scope->getIterableValueType($arrayType), PassedByReference::createNo(), false, null),
new NativeParameterReflection('key', false, $scope->getIterableKeyType($arrayType), PassedByReference::createNo(), false, null),
], new BooleanType());
}

}
62 changes: 62 additions & 0 deletions src/Type/Php/ArrayMapParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\ClosureType;
use PHPStan\Type\FunctionParameterClosureTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use function count;

#[AutowiredService]
final class ArrayMapParameterClosureTypeExtension implements FunctionParameterClosureTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
{
return $functionReflection->getName() === 'array_map' && $parameter->getName() === 'callback';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
if (count($args) < 2) {
return null;
}

$callbackParameters = [];
$argCount = count($args);
for ($i = 1; $i < $argCount; $i++) {
$arg = $args[$i];
$arrayType = $scope->getType($arg->value);
if ($arg->unpack) {
$constantArrays = $arrayType->getConstantArrays();
if (count($constantArrays) === 0) {
return null;
}
foreach ($constantArrays as $constantArray) {
foreach ($constantArray->getValueTypes() as $valueType) {
$callbackParameters[] = $this->createParameter($scope->getIterableValueType($valueType));
}
}
} else {
$callbackParameters[] = $this->createParameter($scope->getIterableValueType($arrayType));
}
}

return new ClosureType($callbackParameters, new MixedType());
}

private function createParameter(Type $type): NativeParameterReflection
{
return new NativeParameterReflection('item', false, $type, PassedByReference::createNo(), false, null);
}

}
45 changes: 45 additions & 0 deletions src/Type/Php/ArrayWalkParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\ClosureType;
use PHPStan\Type\FunctionParameterClosureTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;

#[AutowiredService]
final class ArrayWalkParameterClosureTypeExtension implements FunctionParameterClosureTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
{
return $functionReflection->getName() === 'array_walk' && $parameter->getName() === 'callback';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
if (!isset($args[0])) {
return null;
}

$arrayType = $scope->getType($args[0]->value);
$parameters = [
new NativeParameterReflection('item', false, $scope->getIterableValueType($arrayType), PassedByReference::createReadsArgument(), false, null),
new NativeParameterReflection('key', false, $scope->getIterableKeyType($arrayType), PassedByReference::createNo(), false, null),
];
if (isset($args[2])) {
$parameters[] = new NativeParameterReflection('arg', false, $scope->getType($args[2]->value), PassedByReference::createNo(), false, null);
}

return new ClosureType($parameters, new MixedType());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function bug14904(string $string): void {
'/a|(?<b>b)/',
function (array $match): string {
assertType("array{0: non-empty-string, b: 'b'|null, 1: 'b'|null}", $match);
assertNativeType('array<int|string, string|null>', $match);
assertNativeType("array{0: non-empty-string, b: 'b'|null, 1: 'b'|null}", $match);
return '';
},
$string,
Expand Down
Loading