Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 20 additions & 1 deletion src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FloatType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeCombinator;
use stdClass;
use function count;
Expand Down Expand Up @@ -41,7 +47,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n

$constantStrings = $castType->getConstantStrings();
if (count($constantStrings) < 1) {
return new SpecifiedTypes();
return $this->typeSpecifier->create(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning here, just write

$constantStrings = [...];

with all the valid values and let the foreach do the job.

Also, in the foreach

case 'object':
					$types[] = new ObjectType(stdClass::class);

is not correct as

$a = 1; settype($a, 'object');

gives stdClass but

$a = new \DateTime; settype($a, 'object');

gives a DateTime.

So fix the object case.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The changes address both review comments:

  1. Reuse foreach for non-constant types: Instead of returning a hardcoded union type, the non-constant fallback now populates $constantStrings with all valid settype values (bool, int, float, string, array, object, null) and lets the existing foreach do the work. This avoids duplicating the type mapping logic.

  2. Fix the object case: The object case now correctly handles three scenarios:

    • Value is definitely an object → keeps original type (e.g. DateTime stays DateTime)
    • Value is definitely not an object → produces stdClass
    • Value might be an object → union of the object part of the type with stdClass (e.g. DateTime|stringDateTime|stdClass)

$value,
TypeCombinator::union(
new BooleanType(),
new IntegerType(),
new FloatType(),
new StringType(),
new ArrayType(new MixedType(), new MixedType()),
new ObjectType(stdClass::class),
new NullType(),
),
TypeSpecifierContext::createTruthy(),
$scope,
)->setAlwaysOverwriteTypes();
}

$types = [];
Expand Down
33 changes: 33 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-3250.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace Bug3250;

use stdClass;
use function PHPStan\Testing\assertType;

function castTo(string $value, string $castTo): void
{
$newValue = $value;
settype($newValue, $castTo);

assertType('array|bool|float|int|stdClass|string|null', $newValue);
}

function castToInt(string $value): void
{
$newValue = $value;
settype($newValue, 'int');

assertType('int', $newValue);
}

/**
* @param 'int'|'float' $castTo
*/
function castToIntOrFloat(string $value, string $castTo): void
{
$newValue = $value;
settype($newValue, $castTo);

assertType('float|int', $newValue);
}
Loading