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 7402e40

Browse files
Support conditional types
Part of implementation for phpstan/phpstan#3853
1 parent 4bda9e3 commit 7402e40

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed

‎src/Ast/Type/ConditionalTypeNode.php‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\Type;
4+
5+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
6+
use function sprintf;
7+
8+
class ConditionalTypeNode implements TypeNode
9+
{
10+
11+
use NodeAttributes;
12+
13+
/** @var TypeNode */
14+
public $subjectType;
15+
16+
/** @var TypeNode */
17+
public $targetType;
18+
19+
/** @var TypeNode */
20+
public $trueType;
21+
22+
/** @var TypeNode */
23+
public $falseType;
24+
25+
/** @var bool */
26+
public $negated;
27+
28+
public function __construct(TypeNode $subjectType, TypeNode $targetType, TypeNode $trueType, TypeNode $falseType, bool $negated)
29+
{
30+
$this->subjectType = $subjectType;
31+
$this->targetType = $targetType;
32+
$this->trueType = $trueType;
33+
$this->falseType = $falseType;
34+
$this->negated = $negated;
35+
}
36+
37+
public function __toString(): string
38+
{
39+
return sprintf(
40+
'%s %s %s ? %s : %s',
41+
$this->subjectType,
42+
$this->negated ? 'is not' : 'is',
43+
$this->targetType,
44+
$this->trueType,
45+
$this->falseType
46+
);
47+
}
48+
49+
}

‎src/Parser/TypeParser.php‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
3333

3434
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
3535
$type = $this->parseIntersection($tokens, $type);
36+
} elseif ($tokens->isCurrentTokenValue('is')) {
37+
$type = $this->parseConditional($tokens, $type);
3638
}
3739
}
3840

@@ -44,7 +46,9 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
4446
private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
4547
{
4648
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
49+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
4750
$type = $this->parse($tokens);
51+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
4852
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
4953

5054
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
@@ -157,6 +161,35 @@ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $typ
157161
}
158162

159163

164+
/** @phpstan-impure */
165+
private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType): Ast\Type\TypeNode
166+
{
167+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
168+
169+
$negated = false;
170+
if ($tokens->isCurrentTokenValue('not')) {
171+
$negated = true;
172+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
173+
}
174+
175+
$targetType = $this->parseAtomic($tokens);
176+
177+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
178+
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
179+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
180+
181+
$trueType = $this->parseAtomic($tokens);
182+
183+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
184+
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
185+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
186+
187+
$falseType = $this->parseAtomic($tokens);
188+
189+
return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $trueType, $falseType, $negated);
190+
}
191+
192+
160193
/** @phpstan-impure */
161194
private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode
162195
{

‎tests/PHPStan/Parser/PhpDocParserTest.php‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
3232
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
3333
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
34+
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
3435
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
3536
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
3637
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
@@ -1119,6 +1120,78 @@ public function provideReturnTagsData(): Iterator
11191120
),
11201121
]),
11211122
];
1123+
1124+
yield [
1125+
'OK with conditional type',
1126+
'/** @return Foo is Bar ? never : int */',
1127+
new PhpDocNode([
1128+
new PhpDocTagNode(
1129+
'@return',
1130+
new ReturnTagValueNode(
1131+
new ConditionalTypeNode(
1132+
new IdentifierTypeNode('Foo'),
1133+
new IdentifierTypeNode('Bar'),
1134+
new IdentifierTypeNode('never'),
1135+
new IdentifierTypeNode('int'),
1136+
false
1137+
),
1138+
''
1139+
)
1140+
),
1141+
]),
1142+
];
1143+
1144+
yield [
1145+
'OK with negated conditional type',
1146+
'/** @return Foo is not Bar ? never : int */',
1147+
new PhpDocNode([
1148+
new PhpDocTagNode(
1149+
'@return',
1150+
new ReturnTagValueNode(
1151+
new ConditionalTypeNode(
1152+
new IdentifierTypeNode('Foo'),
1153+
new IdentifierTypeNode('Bar'),
1154+
new IdentifierTypeNode('never'),
1155+
new IdentifierTypeNode('int'),
1156+
true
1157+
),
1158+
''
1159+
)
1160+
),
1161+
]),
1162+
];
1163+
1164+
yield [
1165+
'OK with multiline conditional type',
1166+
'/**
1167+
* @return (
1168+
* T is self::TYPE_STRING
1169+
* ? string
1170+
* : (T is self::TYPE_INT ? int : bool)
1171+
* )
1172+
*/',
1173+
new PhpDocNode([
1174+
new PhpDocTagNode(
1175+
'@return',
1176+
new ReturnTagValueNode(
1177+
new ConditionalTypeNode(
1178+
new IdentifierTypeNode('T'),
1179+
new ConstTypeNode(new ConstFetchNode('self', 'TYPE_STRING')),
1180+
new IdentifierTypeNode('string'),
1181+
new ConditionalTypeNode(
1182+
new IdentifierTypeNode('T'),
1183+
new ConstTypeNode(new ConstFetchNode('self', 'TYPE_INT')),
1184+
new IdentifierTypeNode('int'),
1185+
new IdentifierTypeNode('bool'),
1186+
false
1187+
),
1188+
false
1189+
),
1190+
''
1191+
)
1192+
),
1193+
]),
1194+
];
11221195
}
11231196

11241197

0 commit comments

Comments
(0)

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