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

Sign up
Appearance settings

Commit df1a794

Browse files
Generics type projections (call-site variance)
1 parent aac4411 commit df1a794

File tree

5 files changed

+296
-13
lines changed

5 files changed

+296
-13
lines changed

‎doc/grammars/type.abnf

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ Atomic
2828
/ TokenParenthesesOpen ParenthesizedType TokenParenthesesClose [Array]
2929

3030
Generic
31-
= TokenAngleBracketOpen Type *(TokenComma Type) TokenAngleBracketClose
31+
= TokenAngleBracketOpen GenericTypeArgument *(TokenComma GenericTypeArgument) TokenAngleBracketClose
32+
33+
GenericTypeArgument
34+
= [TokenContravariant / TokenCovariant] Type
35+
/ TokenWildcard
3236

3337
Callable
3438
= TokenParenthesesOpen [CallableParameters] TokenParenthesesClose TokenColon CallableReturnType
@@ -188,6 +192,15 @@ TokenIs
188192
TokenNot
189193
= %s"not" 1*ByteHorizontalWs
190194

195+
TokenContravariant
196+
= %s"contravariant" 1*ByteHorizontalWs
197+
198+
TokenCovariant
199+
= %s"covariant" 1*ByteHorizontalWs
200+
201+
TokenWildcard
202+
= "*" *ByteHorizontalWs
203+
191204
TokenIdentifier
192205
= [ByteBackslash] ByteIdentifierFirst *ByteIdentifierSecond *(ByteBackslash ByteIdentifierFirst *ByteIdentifierSecond) *ByteHorizontalWs
193206

‎src/Ast/Type/GenericTypeNode.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44

55
use PHPStan\PhpDocParser\Ast\NodeAttributes;
66
use function implode;
7+
use function sprintf;
78

89
class GenericTypeNode implements TypeNode
910
{
1011

12+
public const VARIANCE_INVARIANT = 'invariant';
13+
public const VARIANCE_COVARIANT = 'covariant';
14+
public const VARIANCE_CONTRAVARIANT = 'contravariant';
15+
public const VARIANCE_BIVARIANT = 'bivariant';
16+
1117
use NodeAttributes;
1218

1319
/** @var IdentifierTypeNode */
@@ -16,16 +22,33 @@ class GenericTypeNode implements TypeNode
1622
/** @var TypeNode[] */
1723
public $genericTypes;
1824

19-
public function __construct(IdentifierTypeNode $type, array $genericTypes)
25+
/** @var (self::VARIANCE_*)[] */
26+
public $variances;
27+
28+
public function __construct(IdentifierTypeNode $type, array $genericTypes, array $variances = [])
2029
{
2130
$this->type = $type;
2231
$this->genericTypes = $genericTypes;
32+
$this->variances = $variances;
2333
}
2434

2535

2636
public function __toString(): string
2737
{
28-
return $this->type . '<' . implode(', ', $this->genericTypes) . '>';
38+
$genericTypes = [];
39+
40+
foreach ($this->genericTypes as $index => $type) {
41+
$variance = $this->variances[$index] ?? self::VARIANCE_INVARIANT;
42+
if ($variance === self::VARIANCE_INVARIANT) {
43+
$genericTypes[] = (string) $type;
44+
} elseif ($variance === self::VARIANCE_BIVARIANT) {
45+
$genericTypes[] = '*';
46+
} else {
47+
$genericTypes[] = sprintf('%s %s', $variance, $type);
48+
}
49+
}
50+
51+
return $this->type . '<' . implode(', ', $genericTypes) . '>';
2952
}
3053

3154
}

‎src/Parser/TypeParser.php

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,24 +323,54 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
323323
{
324324
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
325325
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
326-
$genericTypes = [$this->parse($tokens)];
326+
327+
$genericTypes = [];
328+
$variances = [];
329+
330+
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
327331

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

330334
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
331335
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
332336
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
333337
// trailing comma case
334-
return new Ast\Type\GenericTypeNode($baseType, $genericTypes);
338+
return new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
335339
}
336-
$genericTypes[]= $this->parse($tokens);
340+
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
337341
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
338342
}
339343

340344
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
341345
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
342346

343-
return new Ast\Type\GenericTypeNode($baseType, $genericTypes);
347+
return new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
348+
}
349+
350+
351+
/**
352+
* @phpstan-impure
353+
* @return array{Ast\Type\TypeNode, Ast\Type\GenericTypeNode::VARIANCE_*}
354+
*/
355+
public function parseGenericTypeArgument(TokenIterator $tokens): array
356+
{
357+
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) {
358+
return [
359+
new Ast\Type\IdentifierTypeNode('mixed'),
360+
Ast\Type\GenericTypeNode::VARIANCE_BIVARIANT,
361+
];
362+
}
363+
364+
if ($tokens->tryConsumeTokenValue('contravariant')) {
365+
$variance = Ast\Type\GenericTypeNode::VARIANCE_CONTRAVARIANT;
366+
} elseif ($tokens->tryConsumeTokenValue('covariant')) {
367+
$variance = Ast\Type\GenericTypeNode::VARIANCE_COVARIANT;
368+
} else {
369+
$variance = Ast\Type\GenericTypeNode::VARIANCE_INVARIANT;
370+
}
371+
372+
$type = $this->parse($tokens);
373+
return [$type, $variance];
344374
}
345375

346376

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /