Skip to content

[PropertyInfo] Add support for phpDocumentor and PHPStan pseudo-types #44451

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 17, 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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
"egulias/email-validator": "~3.0.0",
"masterminds/html5": "<2.6",
"phpdocumentor/reflection-docblock": "<5.2",
"phpdocumentor/type-resolver": "<1.4.0",
"phpdocumentor/type-resolver": "<1.5.1",
"ocramius/proxy-manager": "<2.1",
"phpunit/phpunit": "<5.4.3"
},
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/PropertyInfo/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.1
---

* Add support for phpDocumentor and PHPStan pseudo-types

6.0
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,29 @@ public function constructorTypesProvider()
['ddd', null],
];
}

/**
* @dataProvider pseudoTypesProvider
*/
public function testPseudoTypes($property, array $type)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypesDummy', $property));
}

public function pseudoTypesProvider(): array
{
return [
['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]],
];
}
}

class EmptyDocBlock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,39 @@ public function unionTypesProvider(): array
];
}

/**
* @dataProvider pseudoTypesProvider
*/
public function testPseudoTypes($property, array $type)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PhpStanPseudoTypesDummy', $property));
}

public function pseudoTypesProvider(): array
{
return [
['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['interfaceString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['literalString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]],
['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]],
['negativeInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]],
['nonEmptyArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]],
['nonEmptyList', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))]],
['scalar', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)]],
['number', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]],
['numeric', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)]],
['arrayKey', [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]],
['double', [new Type(Type::BUILTIN_TYPE_FLOAT)]],
];
}

public function testDummyNamespace()
{
$this->assertEquals(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\PropertyInfo\Tests\Fixtures;

/**
* @author Emil Masiakowski <emil.masiakowski@gmail.com>
*/
class PhpStanPseudoTypesDummy extends PseudoTypesDummy
{
/** @var negative-int */
public $negativeInt;

/** @var non-empty-array */
public $nonEmptyArray;

/** @var non-empty-list */
public $nonEmptyList;

/** @var interface-string */
public $interfaceString;

/** @var scalar */
public $scalar;

/** @var array-key */
public $arrayKey;

/** @var number */
public $number;

/** @var numeric */
public $numeric;

/** @var double */
public $double;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\PropertyInfo\Tests\Fixtures;

/**
* @author Emil Masiakowski <emil.masiakowski@gmail.com>
*/
class PseudoTypesDummy
{
/** @var class-string */
public $classString;

/** @var class-string<\stdClass> */
public $classStringGeneric;

/** @var html-escaped-string */
public $htmlEscapedString;

/** @var lowercase-string */
public $lowercaseString;

/** @var non-empty-lowercase-string */
public $nonEmptyLowercaseString;

/** @var non-empty-string */
public $nonEmptyString;

/** @var numeric-string */
public $numericString;

/** @var trait-string */
public $traitString;

/** @var positive-int */
public $positiveInt;

/** @var literal-string */
public $literalString;
}
11 changes: 11 additions & 0 deletions src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@

namespace Symfony\Component\PropertyInfo\Util;

use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\Type as DocType;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\String_;
use Symfony\Component\PropertyInfo\Type;

// Workaround for phpdocumentor/type-resolver < 1.6
Expand Down Expand Up @@ -143,6 +146,14 @@ private function createType(DocType $type, bool $nullable, string $docType = nul
return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
}

if ($type instanceof PseudoType) {
if ($type->underlyingType() instanceof Integer) {
return new Type(Type::BUILTIN_TYPE_INT, $nullable, null);
} elseif ($type->underlyingType() instanceof String_) {
return new Type(Type::BUILTIN_TYPE_STRING, $nullable, null);
}
}

$docType = $this->normalizeType($docType);
[$phpType, $class] = $this->getPhpTypeAndClass($docType);

Expand Down
29 changes: 29 additions & 0 deletions src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array
return $this->compressNullableType($types);
}
if ($node instanceof GenericTypeNode) {
if ('class-string' === $node->type->name) {
return [new Type(Type::BUILTIN_TYPE_STRING)];
}

[$mainType] = $this->extractTypes($node->type, $nameScope);

$collectionKeyTypes = $mainType->getCollectionKeyTypes();
Expand Down Expand Up @@ -158,18 +162,43 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array

switch ($node->name) {
case 'integer':
case 'positive-int':
case 'negative-int':
return [new Type(Type::BUILTIN_TYPE_INT)];
case 'double':
return [new Type(Type::BUILTIN_TYPE_FLOAT)];
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':
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $node->name)];
case 'static':
case 'self':
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())];
case 'class-string':
case 'html-escaped-string':
case 'lowercase-string':
case 'non-empty-lowercase-string':
case 'non-empty-string':
case 'numeric-string':
case 'trait-string':
case 'interface-string':
case 'literal-string':
return [new Type(Type::BUILTIN_TYPE_STRING)];
case 'void':
return [new Type(Type::BUILTIN_TYPE_NULL)];
case 'scalar':
return [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)];
case 'number':
return [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)];
case 'numeric':
return [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)];
case 'array-key':
return [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)];
}

return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveStringName($node->name))];
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/PropertyInfo/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"conflict": {
"phpdocumentor/reflection-docblock": "<5.2",
"phpdocumentor/type-resolver": "<1.4.0",
"phpdocumentor/type-resolver": "<1.5.1",
"symfony/dependency-injection": "<5.4"
},
"suggest": {
Expand Down