1111use PHPStan \PhpDocParser \Ast \ConstExpr \ConstExprNullNode ;
1212use PHPStan \PhpDocParser \Ast \ConstExpr \ConstExprStringNode ;
1313use PHPStan \PhpDocParser \Ast \ConstExpr \ConstExprTrueNode ;
14+ use PHPStan \PhpDocParser \Ast \ConstExpr \ConstFetchNode ;
15+ use PHPStan \PhpDocParser \Ast \Type \ArrayTypeNode ;
1416use PHPStan \PhpDocParser \Ast \Type \ConstTypeNode ;
1517use PHPStan \PhpDocParser \Ast \Type \GenericTypeNode ;
1618use PHPStan \PhpDocParser \Ast \Type \IdentifierTypeNode ;
1719use PHPStan \PhpDocParser \Ast \Type \IntersectionTypeNode ;
1820use PHPStan \PhpDocParser \Ast \Type \NullableTypeNode ;
21+ use PHPStan \PhpDocParser \Ast \Type \OffsetAccessTypeNode ;
1922use PHPStan \PhpDocParser \Ast \Type \TypeNode ;
2023use PHPStan \PhpDocParser \Ast \Type \UnionTypeNode ;
21- use Typhoon \PHPStanTypeParser \CustomTypeParser ;
22- use Typhoon \PHPStanTypeParser \TypeContext ;
24+ use Typhoon \PHPStanTypeParser \Context ;
25+ use Typhoon \PHPStanTypeParser \CustomParser ;
2326use Typhoon \Type \ArrayDefaultT ;
2427use Typhoon \Type \ArrayT ;
28+ use Typhoon \Type \IterableDefaultT ;
29+ use Typhoon \Type \IterableT ;
30+ use Typhoon \Type \ListT ;
2531use Typhoon \Type \Type ;
2632use function Typhoon \Type \andT ;
33+ use function Typhoon \Type \arrayT ;
34+ use function Typhoon \Type \classConstantMaskT ;
35+ use function Typhoon \Type \classConstantT ;
36+ use function Typhoon \Type \constantT ;
2737use function Typhoon \Type \floatRangeT ;
2838use function Typhoon \Type \floatT ;
2939use function Typhoon \Type \intRangeT ;
3040use function Typhoon \Type \intT ;
3141use function Typhoon \Type \nullOrT ;
42+ use function Typhoon \Type \offsetT ;
3243use function Typhoon \Type \orT ;
3344use function Typhoon \Type \stringT ;
3445use const Typhoon \Type \arrayKeyT ;
3546use const Typhoon \Type \arrayT ;
3647use const Typhoon \Type \boolT ;
48+ use const Typhoon \Type \callableT ;
49+ use const Typhoon \Type \closureT ;
3750use const Typhoon \Type \falseT ;
3851use const Typhoon \Type \floatT ;
3952use const Typhoon \Type \intT ;
53+ use const Typhoon \Type \iterableT ;
4054use const Typhoon \Type \literalStringT ;
4155use const Typhoon \Type \lowercaseStringT ;
4256use const Typhoon \Type \mixedT ;
6276 * @internal
6377 * @psalm-internal Typhoon\PHPStanTypeParser
6478 */
65- final readonly class ContextualTypeParser
79+ final readonly class ContextualParser
6680{
6781 public function __construct (
68- private CustomTypeParser $ customTypeParser ,
69- private TypeContext $ context ,
82+ private CustomParser $ customTypeParser ,
83+ private Context $ context ,
7084 ) {}
7185
72- public function parseTypeNode (TypeNode $ node ): Type
86+ public function parse (TypeNode $ node ): Type
7387 {
74- return match (true ) {
75- $ node instanceof NullableTypeNode => nullOrT ($ this ->parseTypeNode ($ node ->type )),
76- $ node instanceof ConstTypeNode => self ::parseConstExpr ($ node ->constExpr ),
77- $ node instanceof IdentifierTypeNode => $ this ->parseIdentifier ($ node ->name ),
78- $ node instanceof GenericTypeNode => $ this ->parseIdentifier ($ node ->type ->name , $ node ->genericTypes ),
79- $ node instanceof UnionTypeNode => orT (...array_map ($ this ->parseTypeNode (...), $ node ->types )),
80- $ node instanceof IntersectionTypeNode => andT (...array_map ($ this ->parseTypeNode (...), $ node ->types )),
81- default => throw new \LogicException (\sprintf ('`%s` is not supported ' , $ node ::class)),
82- };
88+ return $ this ->customTypeParser ->parse ($ node , $ this ->parse (...), $ this ->context )
89+ ?? match (true ) {
90+ $ node instanceof NullableTypeNode => nullOrT ($ this ->parse ($ node ->type )),
91+ $ node instanceof ConstTypeNode => $ this ->parseConstExpr ($ node ->constExpr ),
92+ $ node instanceof IdentifierTypeNode => $ this ->identifier ($ node ->name ),
93+ $ node instanceof GenericTypeNode => $ this ->identifier ($ node ->type ->name , $ node ->genericTypes ),
94+ $ node instanceof UnionTypeNode => orT (...array_map ($ this ->parse (...), $ node ->types )),
95+ $ node instanceof IntersectionTypeNode => andT (...array_map ($ this ->parse (...), $ node ->types )),
96+ $ node instanceof ArrayTypeNode => arrayT (value: $ this ->parse ($ node ->type )),
97+ $ node instanceof OffsetAccessTypeNode => offsetT ($ this ->parse ($ node ->type ), $ this ->parse ($ node ->offset )),
98+ default => throw new \LogicException (\sprintf ('`%s` is not supported ' , $ node ::class)),
99+ };
83100 }
84101
85- private static function parseConstExpr (ConstExprNode $ node ): Type
102+ private function parseConstExpr (ConstExprNode $ node ): Type
86103 {
87104 return match (true ) {
88105 $ node instanceof ConstExprNullNode => nullT,
@@ -97,17 +114,37 @@ private static function parseConstExpr(ConstExprNode $node): Type
97114 default => throw new \LogicException (),
98115 },
99116 $ node instanceof ConstExprStringNode => stringT ($ node ->value ),
117+ $ node instanceof ConstFetchNode => $ this ->constantFetch ($ node ),
100118 default => throw new \LogicException (\sprintf ('PhpDoc node %s is not supported ' , $ node ::class)),
101119 };
102120 }
103121
122+ private function constantFetch (ConstFetchNode $ node ): Type
123+ {
124+ if ($ node ->className === '' ) {
125+ return constantT ($ node ->name );
126+ }
127+
128+ $ class = $ this ->context ->resolveClassName ($ node ->className );
129+
130+ if ($ node ->name === 'class ' ) {
131+ return stringT ($ class );
132+ }
133+
134+ if (str_contains ($ node ->name , '* ' )) {
135+ return classConstantMaskT ($ class , $ node ->name );
136+ }
137+
138+ return classConstantT ($ class , $ node ->name );
139+ }
140+
104141 /**
105142 * @param non-empty-string $name
106143 * @param list<TypeNode> $genericNodes
107144 */
108- private function parseIdentifier (string $ name , array $ genericNodes = []): Type
145+ private function identifier (string $ name , array $ genericNodes = []): Type
109146 {
110- $ atomic = match ($ name ) {
147+ $ singleton = match ($ name ) {
111148 'never ' => neverT,
112149 'void ' => voidT,
113150 'null ' => nullT,
@@ -130,46 +167,55 @@ private function parseIdentifier(string $name, array $genericNodes = []): Type
130167 'numeric ' => numericT,
131168 'scalar ' => scalarT,
132169 'object ' => objectT,
170+ 'Closure ' => closureT,
171+ 'callable ' => callableT,
133172 'mixed ' => mixedT,
134173 default => null ,
135174 };
136175
137- if ($ atomic !== null ) {
176+ if ($ singleton !== null ) {
138177 if ($ genericNodes !== []) {
139178 throw new \LogicException ();
140179 }
141180
142- return $ atomic ;
181+ return $ singleton ;
143182 }
144183
145184 if ($ name === 'int ' || $ name === 'integer ' ) {
146- return $ this ->parseInt ($ genericNodes );
185+ return $ this ->int ($ genericNodes );
147186 }
148187
149- if ($ name === 'float ' ) {
150- return $ this ->parseFloat ($ genericNodes );
188+ if ($ name === 'float ' || $ name === ' double ' ) {
189+ return $ this ->float ($ genericNodes );
151190 }
152191
153- $ templateArguments = array_map ($ this ->parseTypeNode (...), $ genericNodes );
192+ $ templateArguments = array_map ($ this ->parse (...), $ genericNodes );
193+
194+ if ($ name === 'list ' || $ name === 'non-empty-list ' ) {
195+ return $ this ->list ($ templateArguments , isNonEmpty: $ name === 'non-empty-list ' );
196+ }
154197
155198 if ($ name === 'array ' || $ name === 'non-empty-array ' ) {
156- return $ this ->parseArray ($ templateArguments , isNonEmpty: $ name === 'non-empty-array ' );
199+ return $ this ->array ($ templateArguments , isNonEmpty: $ name === 'non-empty-array ' );
200+ }
201+
202+ if ($ name === 'iterable ' ) {
203+ return $ this ->iterable ($ templateArguments );
157204 }
158205
159- return $ this ->customTypeParser ->parseCustomType ($ name , $ templateArguments , $ this ->context )
160- ?? $ this ->context ->resolveNameAsType ($ name , $ templateArguments );
206+ return $ this ->context ->resolveNameAsType ($ name , $ templateArguments );
161207 }
162208
163209 /**
164210 * @param list<TypeNode> $genericNodes
165211 */
166- private function parseInt (array $ genericNodes ): Type
212+ private function int (array $ genericNodes ): Type
167213 {
168214 return match (\count ($ genericNodes )) {
169215 0 => intT,
170216 2 => intRangeT (
171- min: self ::parseIntRangeLimit ($ genericNodes [0 ], 'min ' ),
172- max: self ::parseIntRangeLimit ($ genericNodes [1 ], 'max ' ),
217+ min: self ::intRangeLimit ($ genericNodes [0 ], 'min ' ),
218+ max: self ::intRangeLimit ($ genericNodes [1 ], 'max ' ),
173219 ),
174220 default => throw new \LogicException (\sprintf (
175221 'Int range type should have 2 type arguments, got %d ' ,
@@ -181,24 +227,16 @@ private function parseInt(array $genericNodes): Type
181227 /**
182228 * @param 'min'|'max' $name
183229 */
184- private function parseIntRangeLimit (TypeNode $ type , string $ name ): ?int
230+ private function intRangeLimit (TypeNode $ type , string $ name ): ?int
185231 {
186- if ($ type instanceof IdentifierTypeNode) {
187- if ($ type ->name === $ name ) {
188- return null ;
189- }
232+ $ string = (string ) $ type ;
190233
191- throw new \LogicException ();
234+ if ($ string === $ name ) {
235+ return null ;
192236 }
193237
194- if (!$ type instanceof ConstTypeNode) {
195- throw new \LogicException ();
196- }
197-
198- $ expr = $ type ->constExpr ;
199-
200- if ($ expr instanceof ConstExprIntegerNode && is_numeric ($ expr ->value )) {
201- return (int ) $ expr ->value ;
238+ if (is_numeric ($ string ) && !str_contains ($ string , '. ' )) {
239+ return (int ) $ string ;
202240 }
203241
204242 throw new \LogicException ();
@@ -207,13 +245,13 @@ private function parseIntRangeLimit(TypeNode $type, string $name): ?int
207245 /**
208246 * @param list<TypeNode> $genericNodes
209247 */
210- private function parseFloat (array $ genericNodes ): Type
248+ private function float (array $ genericNodes ): Type
211249 {
212250 return match (\count ($ genericNodes )) {
213251 0 => floatT,
214252 2 => floatRangeT (
215- min: self ::parseFloatRangeLimit ($ genericNodes [0 ], 'min ' ),
216- max: self ::parseFloatRangeLimit ($ genericNodes [1 ], 'max ' ),
253+ min: self ::floatRangeLimit ($ genericNodes [0 ], 'min ' ),
254+ max: self ::floatRangeLimit ($ genericNodes [1 ], 'max ' ),
217255 ),
218256 default => throw new \LogicException (\sprintf (
219257 'Float range type should have 2 type arguments, got %d ' ,
@@ -226,24 +264,16 @@ private function parseFloat(array $genericNodes): Type
226264 * @param 'min'|'max' $name
227265 * @return ?numeric-string
228266 */
229- private function parseFloatRangeLimit (TypeNode $ type , string $ name ): ?string
267+ private function floatRangeLimit (TypeNode $ type , string $ name ): ?string
230268 {
231- if ($ type instanceof IdentifierTypeNode) {
232- if ($ type ->name === $ name ) {
233- return null ;
234- }
269+ $ string = (string ) $ type ;
235270
236- throw new \LogicException ();
271+ if ($ string === $ name ) {
272+ return null ;
237273 }
238274
239- if (!$ type instanceof ConstTypeNode) {
240- throw new \LogicException ();
241- }
242-
243- $ expr = $ type ->constExpr ;
244-
245- if (($ expr instanceof ConstExprFloatNode || $ expr instanceof ConstExprIntegerNode) && is_numeric ($ expr ->value )) {
246- return $ expr ->value ;
275+ if (is_numeric ($ string )) {
276+ return $ string ;
247277 }
248278
249279 throw new \LogicException ();
@@ -252,7 +282,19 @@ private function parseFloatRangeLimit(TypeNode $type, string $name): ?string
252282 /**
253283 * @param list<Type> $templateArguments
254284 */
255- private function parseArray (array $ templateArguments , bool $ isNonEmpty = false ): ArrayDefaultT |ArrayT
285+ private function list (array $ templateArguments , bool $ isNonEmpty = false ): ListT
286+ {
287+ return match ($ number = \count ($ templateArguments )) {
288+ 0 => new ListT (isNonEmpty: $ isNonEmpty ),
289+ 1 => new ListT (valueType: $ templateArguments [0 ], isNonEmpty: $ isNonEmpty ),
290+ default => throw new \LogicException (\sprintf ('list type should have at most 1 type arguments, got %d ' , $ number )),
291+ };
292+ }
293+
294+ /**
295+ * @param list<Type> $templateArguments
296+ */
297+ private function array (array $ templateArguments , bool $ isNonEmpty = false ): ArrayDefaultT |ArrayT
256298 {
257299 return match ($ number = \count ($ templateArguments )) {
258300 0 => $ isNonEmpty ? new ArrayT (isNonEmpty: true ) : arrayT,
@@ -261,4 +303,17 @@ private function parseArray(array $templateArguments, bool $isNonEmpty = false):
261303 default => throw new \LogicException (\sprintf ('array type should have at most 2 type arguments, got %d ' , $ number )),
262304 };
263305 }
306+
307+ /**
308+ * @param list<Type> $templateArguments
309+ */
310+ private function iterable (array $ templateArguments ): IterableDefaultT |IterableT
311+ {
312+ return match ($ number = \count ($ templateArguments )) {
313+ 0 => iterableT,
314+ 1 => new IterableT (valueType: $ templateArguments [0 ]),
315+ 2 => new IterableT (keyType: $ templateArguments [0 ], valueType: $ templateArguments [1 ]),
316+ default => throw new \LogicException (\sprintf ('iterable type should have at most 2 type arguments, got %d ' , $ number )),
317+ };
318+ }
264319}
0 commit comments