Skip to content

Commit

Permalink
parse type projections
Browse files Browse the repository at this point in the history
  • Loading branch information
jiripudil committed Jun 20, 2022
1 parent 4a07085 commit 8b988e6
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 3 deletions.
15 changes: 14 additions & 1 deletion doc/grammars/type.abnf
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ Atomic
/ TokenParenthesesOpen ParenthesizedType TokenParenthesesClose [Array]

Generic
= TokenAngleBracketOpen Type *(TokenComma Type) TokenAngleBracketClose
= TokenAngleBracketOpen GenericTypeArgument *(TokenComma GenericTypeArgument) TokenAngleBracketClose

GenericTypeArgument
= [TokenContravariant / TokenCovariant] Type
/ TokenWildcard

Callable
= TokenParenthesesOpen [CallableParameters] TokenParenthesesClose TokenColon CallableReturnType
Expand Down Expand Up @@ -188,6 +192,15 @@ TokenIs
TokenNot
= %x6E.6F.74 1*ByteHorizontalWs

TokenContravariant
= %x63.6F.6E.74.72.61.76.61.72.69.61.6E.74 1*ByteHorizontalWs

TokenCovariant
= %x63.6F.76.61.72.69.61.6E.74 1*ByteHorizontalWs

TokenWildcard
= "*" *ByteHorizontalWs

TokenIdentifier
= [ByteBackslash] ByteIdentifierFirst *ByteIdentifierSecond *(ByteBackslash ByteIdentifierFirst *ByteIdentifierSecond) *ByteHorizontalWs

Expand Down
17 changes: 17 additions & 0 deletions src/Ast/Type/StarProjectionNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Ast\Type;

use PHPStan\PhpDocParser\Ast\NodeAttributes;

final class StarProjectionNode implements TypeNode
{

use NodeAttributes;

public function __toString(): string
{
return '*';
}

}
33 changes: 33 additions & 0 deletions src/Ast/Type/TypeProjectionNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Ast\Type;

use PHPStan\PhpDocParser\Ast\NodeAttributes;

class TypeProjectionNode implements TypeNode
{

use NodeAttributes;

/** @var TypeNode */
public $type;

/** @var 'covariant'|'contravariant' */
public $variance;

/**
* @param 'covariant'|'contravariant' $variance
*/
public function __construct(TypeNode $type, string $variance)
{
$this->type = $type;
$this->variance = $variance;
}


public function __toString(): string
{
return $this->variance . ' ' . $this->type;
}

}
25 changes: 23 additions & 2 deletions src/Parser/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
{
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$genericTypes = [$this->parse($tokens)];
$genericTypes = [$this->parseGenericTypeArgument($tokens)];

$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

Expand All @@ -333,7 +333,7 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
// trailing comma case
return new Ast\Type\GenericTypeNode($baseType, $genericTypes);
}
$genericTypes[] = $this->parse($tokens);
$genericTypes[] = $this->parseGenericTypeArgument($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
}

Expand All @@ -344,6 +344,27 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
}


/** @phpstan-impure */
public function parseGenericTypeArgument(TokenIterator $tokens): Ast\Type\TypeNode
{
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) {
return new Ast\Type\StarProjectionNode();
}

if ($tokens->tryConsumeTokenValue('contravariant')) {
$variance = 'contravariant';
} elseif ($tokens->tryConsumeTokenValue('covariant')) {
$variance = 'covariant';
} else {
return $this->parse($tokens);
}

$type = $this->parse($tokens);

return new Ast\Type\TypeProjectionNode($type, $variance);
}


/** @phpstan-impure */
private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode
{
Expand Down
47 changes: 47 additions & 0 deletions tests/PHPStan/Parser/TypeParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
use PHPStan\PhpDocParser\Ast\Type\StarProjectionNode;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeProjectionNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -1266,6 +1268,51 @@ public function provideParseData(): array
)
),
],
[
'Foo<covariant Bar, Baz>',
new GenericTypeNode(
new IdentifierTypeNode('Foo'),
[
new TypeProjectionNode(
new IdentifierTypeNode('Bar'),
'covariant'
),
new IdentifierTypeNode('Baz'),
]
),
],
[
'Foo<Bar, contravariant Baz>',
new GenericTypeNode(
new IdentifierTypeNode('Foo'),
[
new IdentifierTypeNode('Bar'),
new TypeProjectionNode(
new IdentifierTypeNode('Baz'),
'contravariant'
),
]
),
],
[
'Foo<covariant>',
new ParserException(
'>',
Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
13,
Lexer::TOKEN_IDENTIFIER
),
],
[
'Foo<Bar, *>',
new GenericTypeNode(
new IdentifierTypeNode('Foo'),
[
new IdentifierTypeNode('Bar'),
new StarProjectionNode(),
]
),
],
];
}

Expand Down

0 comments on commit 8b988e6

Please sign in to comment.