diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index 8b52433a54fe2..5c1bdaad7e2a1 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -355,7 +355,7 @@ public function constructorTypesProvider() /** * @dataProvider unionTypesProvider */ - public function testExtractorUnionTypes(string $property, array $types) + public function testExtractorUnionTypes(string $property, ?array $types) { $this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyUnionType', $property)); } @@ -368,6 +368,8 @@ public function unionTypesProvider(): array ['c', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]], ['d', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING)])])]], ['e', [new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class, true, [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING)])], [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], [new Type(Type::BUILTIN_TYPE_STRING, false, null, true, [], [new Type(Type::BUILTIN_TYPE_OBJECT, false, DefaultValue::class)])])]), new Type(Type::BUILTIN_TYPE_OBJECT, false, ParentDummy::class)]], + ['f', null], + ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [], [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)])]], ]; } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyUnionType.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyUnionType.php index 60af596bad3b3..86ddb8a1650eb 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyUnionType.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyUnionType.php @@ -16,6 +16,9 @@ */ class DummyUnionType { + private const TYPE_A = 'a'; + private const TYPE_B = 'b'; + /** * @var string|int */ @@ -40,4 +43,14 @@ class DummyUnionType * @var (Dummy, (int | (string)[])> | ParentDummy | null) */ public $e; + + /** + * @var self::TYPE_*|null + */ + public $f; + + /** + * @var non-empty-array + */ + public $g; } diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php index b5ed7bb5732ee..f3812ea0f35f4 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php @@ -19,6 +19,7 @@ use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; +use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; @@ -102,6 +103,10 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array if ($node instanceof UnionTypeNode) { $types = []; foreach ($node->types as $type) { + if ($type instanceof ConstTypeNode) { + // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment + return []; + } foreach ($this->extractTypes($type, $nameScope) as $subType) { $types[] = $subType; } @@ -160,7 +165,10 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array case 'integer': return [new Type(Type::BUILTIN_TYPE_INT)]; case 'list': + case 'non-empty-list': return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))]; + case 'non-empty-array': + return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]; case 'mixed': return []; // mixed seems to be ignored in all other extractors case 'parent': diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 5870a657a5abb..fa9483ccf6397 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; use Doctrine\Common\Annotations\AnnotationReader; +use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\LogicException; @@ -721,6 +723,22 @@ public function testAcceptJsonNumber() $this->assertSame(10.0, $serializer->denormalize(['number' => 10], JsonNumber::class, 'jsonld')->number); } + public function testDoesntHaveIssuesWithUnionConstTypes() + { + if (!class_exists(PhpStanExtractor::class) || !class_exists(PhpDocParser::class)) { + $this->markTestSkipped('phpstan/phpdoc-parser required for this test'); + } + + $extractor = new PropertyInfoExtractor([], [new PhpStanExtractor(), new PhpDocExtractor(), new ReflectionExtractor()]); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); + + $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], \get_class(new class() { + /** @var self::*|null */ + public $foo; + }))->foo); + } + public function testExtractAttributesRespectsFormat() { $normalizer = new FormatAndContextAwareNormalizer();