diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index eb73c2932be..daba79560a6 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -530,12 +530,8 @@ public function isCallMapFunctionPure( if ($function_id === 'serialize' && isset($args[0]) && $type_provider) { $serialize_type = $type_provider->getType($args[0]->value); - if ($serialize_type) { - foreach ($serialize_type->getAtomicTypes() as $atomic_serialize_type) { - if ($atomic_serialize_type->isObjectType()) { - return false; - } - } + if ($serialize_type && $serialize_type->canContainObjectType($codebase)) { + return false; } } diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php new file mode 100644 index 00000000000..460a723a474 --- /dev/null +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -0,0 +1,56 @@ +codebase = $codebase; + } + + protected function enterNode(TypeNode $type): ?int + { + if ($type instanceof Union + && ( + UnionTypeComparator::canBeContainedBy($this->codebase, new Union([new TObject()]), $type) + && UnionTypeComparator::canBeContainedBy($this->codebase, $type, new Union([new TObject()])) + ) + || + $type instanceof Atomic + && ( + AtomicTypeComparator::isContainedBy($this->codebase, new TObject(), $type) + || AtomicTypeComparator::isContainedBy($this->codebase, $type, new TObject()) + ) + ) { + $this->contains_object_type = true; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } + + public function matches(): bool + { + return $this->contains_object_type; + } +} diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 6ff34be3156..d7ff6557bb4 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -7,6 +7,7 @@ use Psalm\Codebase; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\Type\TypeCombiner; +use Psalm\Internal\TypeVisitor\CanContainObjectTypeVisitor; use Psalm\Internal\TypeVisitor\ContainsClassLikeVisitor; use Psalm\Internal\TypeVisitor\ContainsLiteralVisitor; use Psalm\Internal\TypeVisitor\FromDocblockSetter; @@ -1492,6 +1493,15 @@ public function containsAnyLiteral(): bool return $literal_visitor->matches(); } + public function canContainObjectType(Codebase $codebase): bool + { + $object_type_visitor = new CanContainObjectTypeVisitor($codebase); + + $object_type_visitor->traverseArray($this->types); + + return $object_type_visitor->matches(); + } + /** * @return list */ diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index 373ad1cf72b..0c46ed635d0 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -751,9 +751,14 @@ public function __wakeup(): void } } - function test(): bool { + function test(Foo|int $foo, mixed $bar, iterable $baz): bool { try { serialize(new Foo()); + serialize([new Foo()]); + serialize([[new Foo()]]); + serialize($foo); + serialize($bar); + serialize($baz); unserialize(""); } catch (\Throwable) { return false;