Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[PropertyInfo] Fix phpstan extractor issues #44578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 25, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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)])]],
];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/
class DummyUnionType
{
private const TYPE_A = 'a';
private const TYPE_B = 'b';

/**
* @var string|int
*/
Expand All @@ -40,4 +43,14 @@ class DummyUnionType
* @var (Dummy<array<mixed, string>, (int | (string<DefaultValue>)[])> | ParentDummy | null)
*/
public $e;

/**
* @var self::TYPE_*|null
*/
public $f;

/**
* @var non-empty-array<string|int>
*/
public $g;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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':
Copy link
Member

@nicolas-grekas nicolas-grekas Dec 17, 2021

Choose a reason for hiding this comment

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

I'm just wondering: why do we want to add this to 5.4 while the other pseudo-types are added on 6.1 (see #44451)?

Copy link
Contributor Author

@ostrolucky ostrolucky Dec 17, 2021

Choose a reason for hiding this comment

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

I think root of the problem is fallback case here on L182. If PhpStanTypeHelper doesn't recognize the type, it returns object. This is what causes issues during 5.3 -> 5.4 upgrade for my second use case. Potentially we could change that to return empty array instead, but that's very aggressive change compared to just adding support for non-empty-array (it might break various cases of @return Foo, @return Foo<bar> and so on. Alternatively, in my opinion backporting changes from #44451 to 5.4 would be fine too. All of those cases that were added there were most likely working fine in 5.3, but got broken in 5.4. non-empty-array just happens to be more often used than the rest of them and that's what blowed up in my projects.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks, that's exactly why I posted #44451 (review)

@derrabus WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't add support for that type here. If we get a proper fallback if the PHPStan extractor reported null here, that's the way to go imho.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well then any idea how to properly make a fallback here? We need a mechanism how to detect object, instead of falling back to object on everything that's not recognized.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, your two answers were not displayed when I posted mine which obviously does not make sense in that context. 🙈

I agree that in that particular case, active support for non-empty-array (and possibly non-empty-list as well) would be the least invasive fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added non-empty-list :)

return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)];
case 'mixed':
return []; // mixed seems to be ignored in all other extractors
case 'parent':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Copy link
Member

Choose a reason for hiding this comment

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

this was passing before the change on PropertyInfo, isn't it? (I'm saying that because deps=low tests still pass)
did you add the test just to increase the coveage? (which is a good thing :) )

Copy link
Contributor Author

@ostrolucky ostrolucky Dec 16, 2021

Choose a reason for hiding this comment

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

Yes I wanted to have some "integration" test with serializer to demonstrate the problem, so to increase coverage yes. We didn't have any tests in serializer that do use PhpStanExtractor. This test doesn't pass without my changes in propertyinfo


public function testExtractAttributesRespectsFormat()
{
$normalizer = new FormatAndContextAwareNormalizer();
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.