Skip to content

Commit c99b242

Browse files
authoredJan 16, 2025··
Fix union of lowercase/uppercase string with empty string
1 parent 0cd6324 commit c99b242

File tree

2 files changed

+172
-6
lines changed

2 files changed

+172
-6
lines changed
 

‎src/Type/TypeCombinator.php

+32-6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
namespace PHPStan\Type;
44

55
use PHPStan\Type\Accessory\AccessoryArrayListType;
6+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
67
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
78
use PHPStan\Type\Accessory\AccessoryType;
9+
use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
810
use PHPStan\Type\Accessory\HasOffsetType;
911
use PHPStan\Type\Accessory\HasOffsetValueType;
1012
use PHPStan\Type\Accessory\HasPropertyType;
@@ -452,7 +454,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array
452454
&& ($b->describe(VerbosityLevel::value()) === 'non-empty-string'
453455
|| $b->describe(VerbosityLevel::value()) === 'non-falsy-string')
454456
) {
455-
return [null, new StringType()];
457+
return [null, self::intersect(
458+
new StringType(),
459+
...self::getAccessoryCaseStringTypes($b),
460+
)];
456461
}
457462

458463
if (
@@ -461,34 +466,55 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array
461466
&& ($a->describe(VerbosityLevel::value()) === 'non-empty-string'
462467
|| $a->describe(VerbosityLevel::value()) === 'non-falsy-string')
463468
) {
464-
return [new StringType(), null];
469+
return [self::intersect(
470+
new StringType(),
471+
...self::getAccessoryCaseStringTypes($a),
472+
), null];
465473
}
466474

467475
if (
468476
$a instanceof ConstantStringType
469477
&& $a->getValue() === '0'
470478
&& $b->describe(VerbosityLevel::value()) === 'non-falsy-string'
471479
) {
472-
return [null, new IntersectionType([
480+
return [null, self::intersect(
473481
new StringType(),
474482
new AccessoryNonEmptyStringType(),
475-
])];
483+
...self::getAccessoryCaseStringTypes($b),
484+
)];
476485
}
477486

478487
if (
479488
$b instanceof ConstantStringType
480489
&& $b->getValue() === '0'
481490
&& $a->describe(VerbosityLevel::value()) === 'non-falsy-string'
482491
) {
483-
return [new IntersectionType([
492+
return [self::intersect(
484493
new StringType(),
485494
new AccessoryNonEmptyStringType(),
486-
]), null];
495+
...self::getAccessoryCaseStringTypes($a),
496+
), null];
487497
}
488498

489499
return null;
490500
}
491501

502+
/**
503+
* @return array<Type>
504+
*/
505+
private static function getAccessoryCaseStringTypes(Type $type): array
506+
{
507+
$accessory = [];
508+
if ($type->isLowercaseString()->yes()) {
509+
$accessory[] = new AccessoryLowercaseStringType();
510+
}
511+
if ($type->isUppercaseString()->yes()) {
512+
$accessory[] = new AccessoryUppercaseStringType();
513+
}
514+
515+
return $accessory;
516+
}
517+
492518
private static function unionWithSubtractedType(
493519
Type $type,
494520
?Type $subtractedType,
+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12312;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
/**
10+
* @param lowercase-string $s
11+
*/
12+
public function sayLowercase(string $s): void
13+
{
14+
if ($s != '') {
15+
assertType('lowercase-string&non-empty-string', $s);
16+
}
17+
assertType('lowercase-string', $s);
18+
}
19+
20+
/**
21+
* @param lowercase-string $s
22+
*/
23+
public function sayLowercase2(string $s): void
24+
{
25+
if ('' != $s) {
26+
assertType('lowercase-string&non-empty-string', $s);
27+
}
28+
assertType('lowercase-string', $s);
29+
}
30+
31+
/**
32+
* @param lowercase-string&non-empty-string $s
33+
*/
34+
public function sayLowercase3(string $s): void
35+
{
36+
if ($s != '0') {
37+
assertType('lowercase-string&non-falsy-string', $s);
38+
}
39+
assertType('lowercase-string&non-empty-string', $s);
40+
}
41+
42+
/**
43+
* @param lowercase-string&non-empty-string $s
44+
*/
45+
public function sayLowercase4(string $s): void
46+
{
47+
if ('0' != $s) {
48+
assertType('lowercase-string&non-falsy-string', $s);
49+
}
50+
assertType('lowercase-string&non-empty-string', $s);
51+
}
52+
53+
/**
54+
* @param uppercase-string $s
55+
*/
56+
public function sayUppercase(string $s): void
57+
{
58+
if ($s != '') {
59+
assertType('non-empty-string&uppercase-string', $s);
60+
}
61+
assertType('uppercase-string', $s);
62+
}
63+
64+
/**
65+
* @param uppercase-string $s
66+
*/
67+
public function sayUppercase2(string $s): void
68+
{
69+
if ('' != $s) {
70+
assertType('non-empty-string&uppercase-string', $s);
71+
}
72+
assertType('uppercase-string', $s);
73+
}
74+
75+
/**
76+
* @param uppercase-string&non-empty-string $s
77+
*/
78+
public function sayUppercase3(string $s): void
79+
{
80+
if ($s != '0') {
81+
assertType('non-falsy-string&uppercase-string', $s);
82+
}
83+
assertType('non-empty-string&uppercase-string', $s);
84+
}
85+
86+
/**
87+
* @param uppercase-string&non-empty-string $s
88+
*/
89+
public function sayUppercase4(string $s): void
90+
{
91+
if ('0' != $s) {
92+
assertType('non-falsy-string&uppercase-string', $s);
93+
}
94+
assertType('non-empty-string&uppercase-string', $s);
95+
}
96+
97+
/**
98+
* @param lowercase-string&uppercase-string $s
99+
*/
100+
public function sayBoth(string $s): void
101+
{
102+
if ($s != '') {
103+
assertType('lowercase-string&non-empty-string&uppercase-string', $s);
104+
}
105+
assertType('lowercase-string&uppercase-string', $s);
106+
}
107+
108+
/**
109+
* @param lowercase-string&uppercase-string $s
110+
*/
111+
public function sayBoth2(string $s): void
112+
{
113+
if ('' != $s) {
114+
assertType('lowercase-string&non-empty-string&uppercase-string', $s);
115+
}
116+
assertType('lowercase-string&uppercase-string', $s);
117+
}
118+
119+
/**
120+
* @param lowercase-string&uppercase-string&non-empty-string $s
121+
*/
122+
public function sayBoth3(string $s): void
123+
{
124+
if ($s != '0') {
125+
assertType('lowercase-string&non-falsy-string&uppercase-string', $s);
126+
}
127+
assertType('lowercase-string&non-empty-string&uppercase-string', $s);
128+
}
129+
130+
/**
131+
* @param lowercase-string&uppercase-string&non-empty-string $s
132+
*/
133+
public function sayBoth4(string $s): void
134+
{
135+
if ('0' != $s) {
136+
assertType('lowercase-string&non-falsy-string&uppercase-string', $s);
137+
}
138+
assertType('lowercase-string&non-empty-string&uppercase-string', $s);
139+
}
140+
}

0 commit comments

Comments
 (0)
Please sign in to comment.