Add support for constant types.

phpstan supports contant definitions and expressions to
link to constants.
This commit is contained in:
Jaapio
2022-11-04 11:21:53 +01:00
parent 8d57d3da2d
commit 50bf16734e
8 changed files with 206 additions and 62 deletions
+35 -4
View File
@@ -4,10 +4,15 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\PseudoTypes\ArrayShape; use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem; use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\PseudoTypes\FloatValue;
use phpDocumentor\Reflection\PseudoTypes\IntegerRange; use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
use phpDocumentor\Reflection\PseudoTypes\IntegerValue;
use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\PseudoTypes\StringValue;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Array_;
@@ -20,6 +25,10 @@ use phpDocumentor\Reflection\Types\InterfaceString;
use phpDocumentor\Reflection\Types\Intersection; use phpDocumentor\Reflection\Types\Intersection;
use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\This; use phpDocumentor\Reflection\Types\This;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
@@ -48,10 +57,12 @@ use function strtolower;
final class TypeFactory final class TypeFactory
{ {
private TypeResolver $resolver; private TypeResolver $resolver;
private FqsenResolver $fqsenResolver;
public function __construct(TypeResolver $resolver) public function __construct(TypeResolver $resolver, FqsenResolver $fqsenResolver)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->fqsenResolver = $fqsenResolver;
} }
public function createType(?TypeNode $type, ?Context $context): ?Type public function createType(?TypeNode $type, ?Context $context): ?Type
@@ -82,7 +93,7 @@ final class TypeFactory
return $this->createFromCallable($type, $context); return $this->createFromCallable($type, $context);
case ConstTypeNode::class: case ConstTypeNode::class:
return null; return $this->createFromConst($type, $context);
case GenericTypeNode::class: case GenericTypeNode::class:
return $this->createFromGeneric($type, $context); return $this->createFromGeneric($type, $context);
@@ -144,12 +155,12 @@ final class TypeFactory
case 'class-string': case 'class-string':
return new ClassString( return new ClassString(
$this->createType($type->genericTypes[0], $context)->getFqsen() $this->fqsenResolver->resolve((string) $type->genericTypes[0], $context)
); );
case 'interface-string': case 'interface-string':
return new InterfaceString( return new InterfaceString(
$this->createType($type->genericTypes[0], $context)->getFqsen() $this->fqsenResolver->resolve((string) $type->genericTypes[0], $context)
); );
case 'list': case 'list':
@@ -180,4 +191,24 @@ final class TypeFactory
{ {
return new Callable_(); return new Callable_();
} }
private function createFromConst(ConstTypeNode $type, ?Context $context): ?Type
{
switch (get_class($type->constExpr)) {
case ConstExprIntegerNode::class:
return new IntegerValue((int) $type->constExpr->value);
case ConstExprFloatNode::class:
return new FloatValue((float) $type->constExpr->value);
case ConstExprStringNode::class:
return new StringValue($type->constExpr->value);
case ConstFetchNode::class:
return new ConstExpression(
$this->fqsenResolver->resolve($type->constExpr->className, $context),
$type->constExpr->name
);
}
}
} }
+1 -1
View File
@@ -70,7 +70,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
$tagFactory = new StandardTagFactory($fqsenResolver); $tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory); $descriptionFactory = new DescriptionFactory($tagFactory);
$typeResolver = new TypeResolver($fqsenResolver); $typeResolver = new TypeResolver($fqsenResolver);
$typeFactory = new TypeFactory($typeResolver); $typeFactory = new TypeFactory($typeResolver, $fqsenResolver);
$phpstanTagFactory = new AbstractPHPStanFactory( $phpstanTagFactory = new AbstractPHPStanFactory(
new ParamFactory($typeFactory, $descriptionFactory), new ParamFactory($typeFactory, $descriptionFactory),
+44
View File
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Mixed_;
use function sprintf;
final class ConstExpression implements PseudoType
{
private Fqsen $owner;
private string $expression;
public function __construct(Fqsen $owner, string $expression)
{
$this->owner = $owner;
$this->expression = $expression;
}
public function getOwner(): Fqsen
{
return $this->owner;
}
public function getExpression(): string
{
return $this->expression;
}
public function underlyingType(): Type
{
return new Mixed_();
}
public function __toString(): string
{
return sprintf('%s::%s', $this->owner, $this->expression);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Float_;
class FloatValue implements PseudoType
{
private float $value;
public function __construct(float $value)
{
$this->value = $value;
}
public function getValue(): float
{
return $this->value;
}
public function underlyingType(): Type
{
return new Float_();
}
public function __toString(): string
{
return (string) $this->value;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Integer;
final class IntegerValue implements PseudoType
{
private int $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function getValue(): int
{
return $this->value;
}
public function underlyingType(): Type
{
return new Integer();
}
public function __toString(): string
{
return (string) $this->value;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Float_;
use function sprintf;
class StringValue implements PseudoType
{
private string $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
public function underlyingType(): Type
{
return new Float_();
}
public function __toString(): string
{
return sprintf('"%s"', $this->value);
}
}
@@ -39,7 +39,7 @@ abstract class TagFactoryTestCase extends TestCase
public function giveTypeFactory(): TypeFactory public function giveTypeFactory(): TypeFactory
{ {
return new TypeFactory(new TypeResolver(new FqsenResolver())); return new TypeFactory(new TypeResolver(new FqsenResolver()), new FqsenResolver());
} }
public function givenDescriptionFactory(): DescriptionFactory public function givenDescriptionFactory(): DescriptionFactory
@@ -15,8 +15,13 @@ namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\PseudoTypes\FloatValue;
use phpDocumentor\Reflection\PseudoTypes\IntegerRange; use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
use phpDocumentor\Reflection\PseudoTypes\IntegerValue;
use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\PseudoTypes\StringValue;
use phpDocumentor\Reflection\PseudoTypes\True_;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Array_;
@@ -50,6 +55,8 @@ final class TypeFactoryTest extends TestCase
* @dataProvider typeProvider * @dataProvider typeProvider
* @dataProvider genericsProvider * @dataProvider genericsProvider
* @dataProvider callableProvider * @dataProvider callableProvider
* @dataProvider constExpressions
* @testdox create type from $type
*/ */
public function testTypeBuilding(string $type, Type $expected): void public function testTypeBuilding(string $type, Type $expected): void
{ {
@@ -58,8 +65,9 @@ final class TypeFactoryTest extends TestCase
$constParser = new ConstExprParser(); $constParser = new ConstExprParser();
$parser = new TypeParser($constParser); $parser = new TypeParser($constParser);
$ast = $parser->parse(new TokenIterator($tokens)); $ast = $parser->parse(new TokenIterator($tokens));
$fqsenResolver = new FqsenResolver();
$factory = new TypeFactory(new TypeResolver(new FqsenResolver())); $factory = new TypeFactory(new TypeResolver($fqsenResolver), $fqsenResolver);
$actual = $factory->createType($ast, new Context('phpDocumentor')); $actual = $factory->createType($ast, new Context('phpDocumentor'));
self::assertEquals($expected, $actual); self::assertEquals($expected, $actual);
@@ -219,72 +227,29 @@ final class TypeFactoryTest extends TestCase
public function constExpressions(): array public function constExpressions(): array
{ {
return [ return [
['Foo::FOO_CONSTANT'],
[ [
'123', '123',
//new ConstTypeNode(new ConstExprIntegerNode('123')), new IntegerValue(123),
],
[
'true',
new True_(),
], ],
[ [
'123.2', '123.2',
//new ConstTypeNode(new ConstExprFloatNode('123.2')), new FloatValue(123.2),
], ],
[ [
'"bar"', '"bar"',
//new ConstTypeNode(new ConstExprStringNode('bar')), new StringValue('bar'),
],
[
'Foo::FOO_CONSTANT',
new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_CONSTANT'),
], ],
[ [
'Foo::FOO_*', 'Foo::FOO_*',
//new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')), new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_*'),
],
[
'Foo::FOO_*BAR',
//new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*BAR')),
],
[
'Foo::*FOO*',
//new ConstTypeNode(new ConstFetchNode('Foo', '*FOO*')),
],
[
'Foo::A*B*C',
//new ConstTypeNode(new ConstFetchNode('Foo', 'A*B*C')),
],
[
'self::*BAR',
//new ConstTypeNode(new ConstFetchNode('self', '*BAR')),
],
[
'Foo::*',
//new ConstTypeNode(new ConstFetchNode('Foo', '*')),
],
[
'Foo::**',
//new ConstTypeNode(new ConstFetchNode('Foo', '*')), // fails later in PhpDocParser
//Lexer::TOKEN_WILDCARD,
],
[
'Foo::*a',
//new ConstTypeNode(new ConstFetchNode('Foo', '*a')),
],
[
'( "foo" | Foo::FOO_* )',
// new UnionTypeNode([
// new ConstTypeNode(new ConstExprStringNode('foo')),
// new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')),
// ]),
],
[
'DateTimeImmutable::*|DateTime::*',
// new UnionTypeNode([
// new ConstTypeNode(new ConstFetchNode('DateTimeImmutable', '*')),
// new ConstTypeNode(new ConstFetchNode('DateTime', '*')),
// ]),
],
[
'ParameterTier::*|null',
// new UnionTypeNode([
// new ConstTypeNode(new ConstFetchNode('ParameterTier', '*')),
// new IdentifierTypeNode('null'),
// ]),
], ],
]; ];
} }