3
3
namespace PHPStan \Type ;
4
4
5
5
use PHPStan \Php \PhpVersion ;
6
+ use PHPStan \PhpDocParser \Ast \Type \ArrayShapeNode ;
6
7
use PHPStan \PhpDocParser \Ast \Type \GenericTypeNode ;
7
8
use PHPStan \PhpDocParser \Ast \Type \IdentifierTypeNode ;
8
9
use PHPStan \PhpDocParser \Ast \Type \IntersectionTypeNode ;
45
46
use function ksort ;
46
47
use function md5 ;
47
48
use function sprintf ;
48
- use function str_starts_with ;
49
49
use function strcasecmp ;
50
50
use function strlen ;
51
51
use function substr ;
@@ -347,6 +347,10 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes)
347
347
348
348
$ nonEmptyStr = false ;
349
349
$ nonFalsyStr = false ;
350
+ $ isList = $ this ->isList ()->yes ();
351
+ $ isArray = $ this ->isArray ()->yes ();
352
+ $ isNonEmptyArray = $ this ->isIterableAtLeastOnce ()->yes ();
353
+ $ describedTypes = [];
350
354
foreach ($ this ->getSortedTypes () as $ i => $ type ) {
351
355
if ($ type instanceof AccessoryNonEmptyStringType
352
356
|| $ type instanceof AccessoryLiteralStringType
@@ -379,10 +383,45 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes)
379
383
$ skipTypeNames [] = 'string ' ;
380
384
continue ;
381
385
}
382
- if ($ type instanceof NonEmptyArrayType || $ type instanceof AccessoryArrayListType) {
383
- $ typesToDescribe [$ i ] = $ type ;
384
- $ skipTypeNames [] = 'array ' ;
385
- continue ;
386
+ if ($ isList || $ isArray ) {
387
+ if ($ type instanceof ArrayType) {
388
+ $ keyType = $ type ->getKeyType ();
389
+ $ valueType = $ type ->getItemType ();
390
+ if ($ isList ) {
391
+ $ isMixedValueType = $ valueType instanceof MixedType && $ valueType ->describe (VerbosityLevel::precise ()) === 'mixed ' && !$ valueType ->isExplicitMixed ();
392
+ $ valueTypeDescription = '' ;
393
+ if (!$ isMixedValueType ) {
394
+ $ valueTypeDescription = sprintf ('<%s> ' , $ valueType ->describe ($ level ));
395
+ }
396
+
397
+ $ describedTypes [$ i ] = ($ isNonEmptyArray ? 'non-empty-list ' : 'list ' ) . $ valueTypeDescription ;
398
+ } else {
399
+ $ isMixedKeyType = $ keyType instanceof MixedType && $ keyType ->describe (VerbosityLevel::precise ()) === 'mixed ' && !$ keyType ->isExplicitMixed ();
400
+ $ isMixedValueType = $ valueType instanceof MixedType && $ valueType ->describe (VerbosityLevel::precise ()) === 'mixed ' && !$ valueType ->isExplicitMixed ();
401
+ $ typeDescription = '' ;
402
+ if (!$ isMixedKeyType ) {
403
+ $ typeDescription = sprintf ('<%s, %s> ' , $ keyType ->describe ($ level ), $ valueType ->describe ($ level ));
404
+ } elseif (!$ isMixedValueType ) {
405
+ $ typeDescription = sprintf ('<%s> ' , $ valueType ->describe ($ level ));
406
+ }
407
+
408
+ $ describedTypes [$ i ] = ($ isNonEmptyArray ? 'non-empty-array ' : 'array ' ) . $ typeDescription ;
409
+ }
410
+ continue ;
411
+ } elseif ($ type instanceof ConstantArrayType) {
412
+ $ description = $ type ->describe ($ level );
413
+ $ descriptionWithoutKind = substr ($ description , strlen ('array ' ));
414
+ $ begin = $ isList ? 'list ' : 'array ' ;
415
+ if ($ isNonEmptyArray && !$ type ->isIterableAtLeastOnce ()->yes ()) {
416
+ $ begin = 'non-empty- ' . $ begin ;
417
+ }
418
+
419
+ $ describedTypes [$ i ] = $ begin . $ descriptionWithoutKind ;
420
+ continue ;
421
+ }
422
+ if ($ type instanceof NonEmptyArrayType || $ type instanceof AccessoryArrayListType) {
423
+ continue ;
424
+ }
386
425
}
387
426
388
427
if ($ type instanceof CallableType && $ type ->isCommonCallable ()) {
@@ -404,7 +443,6 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes)
404
443
$ typesToDescribe [$ i ] = $ type ;
405
444
}
406
445
407
- $ describedTypes = [];
408
446
foreach ($ baseTypes as $ i => $ type ) {
409
447
$ typeDescription = $ type ->describe ($ level );
410
448
@@ -418,36 +456,6 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes)
418
456
}
419
457
}
420
458
421
- if (
422
- str_starts_with ($ typeDescription , 'array< ' )
423
- && in_array ('array ' , $ skipTypeNames , true )
424
- ) {
425
- $ nonEmpty = false ;
426
- $ typeName = 'array ' ;
427
- foreach ($ typesToDescribe as $ j => $ typeToDescribe ) {
428
- if (
429
- $ typeToDescribe instanceof AccessoryArrayListType
430
- && substr ($ typeDescription , 0 , strlen ('array<int<0, max>, ' )) === 'array<int<0, max>, '
431
- ) {
432
- $ typeName = 'list ' ;
433
- $ typeDescription = 'array< ' . substr ($ typeDescription , strlen ('array<int<0, max>, ' ));
434
- } elseif ($ typeToDescribe instanceof NonEmptyArrayType) {
435
- $ nonEmpty = true ;
436
- } else {
437
- continue ;
438
- }
439
-
440
- unset($ typesToDescribe [$ j ]);
441
- }
442
-
443
- if ($ nonEmpty ) {
444
- $ typeName = 'non-empty- ' . $ typeName ;
445
- }
446
-
447
- $ describedTypes [$ i ] = $ typeName . '< ' . substr ($ typeDescription , strlen ('array< ' ));
448
- continue ;
449
- }
450
-
451
459
if (in_array ($ typeDescription , $ skipTypeNames , true )) {
452
460
continue ;
453
461
}
@@ -1139,6 +1147,10 @@ public function toPhpDocNode(): TypeNode
1139
1147
1140
1148
$ nonEmptyStr = false ;
1141
1149
$ nonFalsyStr = false ;
1150
+ $ isList = $ this ->isList ()->yes ();
1151
+ $ isArray = $ this ->isArray ()->yes ();
1152
+ $ isNonEmptyArray = $ this ->isIterableAtLeastOnce ()->yes ();
1153
+ $ describedTypes = [];
1142
1154
1143
1155
foreach ($ this ->getSortedTypes () as $ i => $ type ) {
1144
1156
if ($ type instanceof AccessoryNonEmptyStringType
@@ -1168,11 +1180,70 @@ public function toPhpDocNode(): TypeNode
1168
1180
$ skipTypeNames [] = 'string ' ;
1169
1181
continue ;
1170
1182
}
1171
- if ($ type instanceof NonEmptyArrayType || $ type instanceof AccessoryArrayListType) {
1172
- $ typesToDescribe [$ i ] = $ type ;
1173
- $ skipTypeNames [] = 'array ' ;
1174
- continue ;
1183
+
1184
+ if ($ isList || $ isArray ) {
1185
+ if ($ type instanceof ArrayType) {
1186
+ $ keyType = $ type ->getKeyType ();
1187
+ $ valueType = $ type ->getItemType ();
1188
+ if ($ isList ) {
1189
+ $ isMixedValueType = $ valueType instanceof MixedType && $ valueType ->describe (VerbosityLevel::precise ()) === 'mixed ' && !$ valueType ->isExplicitMixed ();
1190
+ $ identifierTypeNode = new IdentifierTypeNode ($ isNonEmptyArray ? 'non-empty-list ' : 'list ' );
1191
+ if (!$ isMixedValueType ) {
1192
+ $ describedTypes [$ i ] = new GenericTypeNode ($ identifierTypeNode , [
1193
+ $ valueType ->toPhpDocNode (),
1194
+ ]);
1195
+ } else {
1196
+ $ describedTypes [$ i ] = $ identifierTypeNode ;
1197
+ }
1198
+ } else {
1199
+ $ isMixedKeyType = $ keyType instanceof MixedType && $ keyType ->describe (VerbosityLevel::precise ()) === 'mixed ' && !$ keyType ->isExplicitMixed ();
1200
+ $ isMixedValueType = $ valueType instanceof MixedType && $ valueType ->describe (VerbosityLevel::precise ()) === 'mixed ' && !$ valueType ->isExplicitMixed ();
1201
+ $ identifierTypeNode = new IdentifierTypeNode ($ isNonEmptyArray ? 'non-empty-array ' : 'array ' );
1202
+ if (!$ isMixedKeyType ) {
1203
+ $ describedTypes [$ i ] = new GenericTypeNode ($ identifierTypeNode , [
1204
+ $ keyType ->toPhpDocNode (),
1205
+ $ valueType ->toPhpDocNode (),
1206
+ ]);
1207
+ } elseif (!$ isMixedValueType ) {
1208
+ $ describedTypes [$ i ] = new GenericTypeNode ($ identifierTypeNode , [
1209
+ $ valueType ->toPhpDocNode (),
1210
+ ]);
1211
+ } else {
1212
+ $ describedTypes [$ i ] = $ identifierTypeNode ;
1213
+ }
1214
+ }
1215
+ continue ;
1216
+ } elseif ($ type instanceof ConstantArrayType) {
1217
+ $ constantArrayTypeNode = $ type ->toPhpDocNode ();
1218
+ if ($ constantArrayTypeNode instanceof ArrayShapeNode) {
1219
+ $ newKind = $ constantArrayTypeNode ->kind ;
1220
+ if ($ isList ) {
1221
+ if ($ isNonEmptyArray && !$ type ->isIterableAtLeastOnce ()->yes ()) {
1222
+ $ newKind = ArrayShapeNode::KIND_NON_EMPTY_LIST ;
1223
+ } else {
1224
+ $ newKind = ArrayShapeNode::KIND_LIST ;
1225
+ }
1226
+ } elseif ($ isNonEmptyArray && !$ type ->isIterableAtLeastOnce ()->yes ()) {
1227
+ $ newKind = ArrayShapeNode::KIND_NON_EMPTY_ARRAY ;
1228
+ }
1229
+
1230
+ if ($ newKind !== $ constantArrayTypeNode ->kind ) {
1231
+ if ($ constantArrayTypeNode ->sealed ) {
1232
+ $ constantArrayTypeNode = ArrayShapeNode::createSealed ($ constantArrayTypeNode ->items , $ newKind );
1233
+ } else {
1234
+ $ constantArrayTypeNode = ArrayShapeNode::createUnsealed ($ constantArrayTypeNode ->items , $ constantArrayTypeNode ->unsealedType , $ newKind );
1235
+ }
1236
+ }
1237
+
1238
+ $ describedTypes [$ i ] = $ constantArrayTypeNode ;
1239
+ continue ;
1240
+ }
1241
+ }
1242
+ if ($ type instanceof NonEmptyArrayType || $ type instanceof AccessoryArrayListType) {
1243
+ continue ;
1244
+ }
1175
1245
}
1246
+
1176
1247
if (!$ type instanceof AccessoryType) {
1177
1248
$ baseTypes [$ i ] = $ type ;
1178
1249
continue ;
@@ -1186,7 +1257,6 @@ public function toPhpDocNode(): TypeNode
1186
1257
$ typesToDescribe [$ i ] = $ type ;
1187
1258
}
1188
1259
1189
- $ describedTypes = [];
1190
1260
foreach ($ baseTypes as $ i => $ type ) {
1191
1261
$ typeNode = $ type ->toPhpDocNode ();
1192
1262
if ($ typeNode instanceof GenericTypeNode && $ typeNode ->type ->name === 'array ' ) {
0 commit comments