Skip to content

Commit 18c75a2

Browse files
author
Michael Voříšek
authored
Fix substracted union type describe
1 parent d517bb4 commit 18c75a2

19 files changed

+52
-42
lines changed

src/Type/MixedType.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,19 @@ public function describe(VerbosityLevel $level): string
447447
function () use ($level): string {
448448
$description = 'mixed';
449449
if ($this->subtractedType !== null) {
450-
$description .= sprintf('~%s', $this->subtractedType->describe($level));
450+
$description .= $this->subtractedType instanceof UnionType
451+
? sprintf('~(%s)', $this->subtractedType->describe($level))
452+
: sprintf('~%s', $this->subtractedType->describe($level));
451453
}
452454

453455
return $description;
454456
},
455457
function () use ($level): string {
456458
$description = 'mixed';
457459
if ($this->subtractedType !== null) {
458-
$description .= sprintf('~%s', $this->subtractedType->describe($level));
460+
$description .= $this->subtractedType instanceof UnionType
461+
? sprintf('~(%s)', $this->subtractedType->describe($level))
462+
: sprintf('~%s', $this->subtractedType->describe($level));
459463
}
460464

461465
if ($this->isExplicitMixed) {

src/Type/ObjectType.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,9 @@ public function describe(VerbosityLevel $level): string
487487
$preciseWithSubtracted = function () use ($level): string {
488488
$description = $this->className;
489489
if ($this->subtractedType !== null) {
490-
$description .= sprintf('~%s', $this->subtractedType->describe($level));
490+
$description .= $this->subtractedType instanceof UnionType
491+
? sprintf('~(%s)', $this->subtractedType->describe($level))
492+
: sprintf('~%s', $this->subtractedType->describe($level));
491493
}
492494

493495
return $description;
@@ -538,7 +540,9 @@ private function describeCache(): string
538540
}
539541

540542
if ($this->subtractedType !== null) {
541-
$description .= sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache()));
543+
$description .= $this->subtractedType instanceof UnionType
544+
? sprintf('~(%s)', $this->subtractedType->describe(VerbosityLevel::cache()))
545+
: sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache()));
542546
}
543547

544548
$reflection = $this->classReflection;

src/Type/ObjectWithoutClassType.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ public function describe(VerbosityLevel $level): string
132132
function () use ($level): string {
133133
$description = 'object';
134134
if ($this->subtractedType !== null) {
135-
$description .= sprintf('~%s', $this->subtractedType->describe($level));
135+
$description .= $this->subtractedType instanceof UnionType
136+
? sprintf('~(%s)', $this->subtractedType->describe($level))
137+
: sprintf('~%s', $this->subtractedType->describe($level));
136138
}
137139

138140
return $description;

tests/PHPStan/Analyser/TypeSpecifierTest.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class TypeSpecifierTest extends PHPStanTestCase
4444
{
4545

4646
private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array{}|false|null';
47-
private const TRUTHY_TYPE_DESCRIPTION = 'mixed~' . self::FALSEY_TYPE_DESCRIPTION;
47+
private const TRUTHY_TYPE_DESCRIPTION = 'mixed~(' . self::FALSEY_TYPE_DESCRIPTION . ')';
4848
private const SURE_NOT_FALSEY = '~' . self::FALSEY_TYPE_DESCRIPTION;
4949
private const SURE_NOT_TRUTHY = '~' . self::TRUTHY_TYPE_DESCRIPTION;
5050

@@ -819,10 +819,10 @@ public function dataCondition(): iterable
819819
new LNumber(3),
820820
),
821821
[
822-
'$n' => 'mixed~int<3, max>|true',
822+
'$n' => 'mixed~(int<3, max>|true)',
823823
],
824824
[
825-
'$n' => 'mixed~0.0|int<min, 2>|false|null',
825+
'$n' => 'mixed~(0.0|int<min, 2>|false|null)',
826826
],
827827
],
828828
[
@@ -831,10 +831,10 @@ public function dataCondition(): iterable
831831
new LNumber(PHP_INT_MIN),
832832
),
833833
[
834-
'$n' => 'mixed~int<' . PHP_INT_MIN . ', max>|true',
834+
'$n' => 'mixed~(int<' . PHP_INT_MIN . ', max>|true)',
835835
],
836836
[
837-
'$n' => 'mixed~0.0|false|null',
837+
'$n' => 'mixed~(0.0|false|null)',
838838
],
839839
],
840840
[
@@ -843,7 +843,7 @@ public function dataCondition(): iterable
843843
new LNumber(PHP_INT_MAX),
844844
),
845845
[
846-
'$n' => 'mixed~0.0|bool|int<min, ' . PHP_INT_MAX . '>|null',
846+
'$n' => 'mixed~(0.0|bool|int<min, ' . PHP_INT_MAX . '>|null)',
847847
],
848848
[
849849
'$n' => 'mixed',
@@ -858,7 +858,7 @@ public function dataCondition(): iterable
858858
'$n' => 'mixed~int<' . (PHP_INT_MIN + 1) . ', max>',
859859
],
860860
[
861-
'$n' => 'mixed~0.0|bool|int<min, ' . PHP_INT_MIN . '>|null',
861+
'$n' => 'mixed~(0.0|bool|int<min, ' . PHP_INT_MIN . '>|null)',
862862
],
863863
],
864864
[
@@ -867,10 +867,10 @@ public function dataCondition(): iterable
867867
new LNumber(PHP_INT_MAX),
868868
),
869869
[
870-
'$n' => 'mixed~0.0|int<min, ' . (PHP_INT_MAX - 1) . '>|false|null',
870+
'$n' => 'mixed~(0.0|int<min, ' . (PHP_INT_MAX - 1) . '>|false|null)',
871871
],
872872
[
873-
'$n' => 'mixed~int<' . PHP_INT_MAX . ', max>|true',
873+
'$n' => 'mixed~(int<' . PHP_INT_MAX . ', max>|true)',
874874
],
875875
],
876876
[
@@ -885,10 +885,10 @@ public function dataCondition(): iterable
885885
),
886886
),
887887
[
888-
'$n' => 'mixed~0.0|int<min, 2>|int<6, max>|false|null',
888+
'$n' => 'mixed~(0.0|int<min, 2>|int<6, max>|false|null)',
889889
],
890890
[
891-
'$n' => 'mixed~int<3, 5>|true',
891+
'$n' => 'mixed~(int<3, 5>|true)',
892892
],
893893
],
894894
[
@@ -1250,7 +1250,7 @@ public function dataCondition(): iterable
12501250
),
12511251
[
12521252
'$foo' => 'non-empty-array',
1253-
'count($foo)' => 'mixed~0.0|int<min, 1>|false|null',
1253+
'count($foo)' => 'mixed~(0.0|int<min, 1>|false|null)',
12541254
],
12551255
[],
12561256
],

tests/PHPStan/Analyser/nsrt/assert-empty.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ function ($var) {
2525

2626
function ($var) {
2727
assertNotEmpty($var);
28-
assertType("mixed~0|0.0|''|'0'|array{}|false|null", $var);
28+
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $var);
2929
};

tests/PHPStan/Analyser/nsrt/bug-10189.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function file_managed_file_save_upload(): array {
2020
}
2121
assertType('non-empty-array|Bug10189\SomeInterface', $files);
2222
$files = array_filter($files);
23-
assertType("array<mixed~0|0.0|''|'0'|array{}|false|null>", $files);
23+
assertType("array<mixed~(0|0.0|''|'0'|array{}|false|null)>", $files);
2424

2525
return empty($files) ? [] : [1,2];
2626
}

tests/PHPStan/Analyser/nsrt/bug-3991.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static function email($config = null)
2626
assertType('array{}|null', $config);
2727
$config = new \stdClass();
2828
} elseif (! (is_array($config) || $config instanceof \stdClass)) {
29-
assertNativeType('mixed~0|0.0|\'\'|\'0\'|array{}|stdClass|false|null', $config);
29+
assertNativeType('mixed~(0|0.0|\'\'|\'0\'|array{}|stdClass|false|null)', $config);
3030
assertType('*NEVER*', $config);
3131
}
3232

tests/PHPStan/Analyser/nsrt/bug-3993.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function doFoo($arguments)
1313
return;
1414
}
1515

16-
assertType('mixed~array{}|null', $arguments);
16+
assertType('mixed~(array{}|null)', $arguments);
1717

1818
array_shift($arguments);
1919

tests/PHPStan/Analyser/nsrt/bug-4117.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function broken(int $key)
3232
$item = $this->items[$key] ?? null;
3333
assertType('T of mixed~null (class Bug4117Types\GenericList, argument)|null', $item);
3434
if ($item) {
35-
assertType("T of mixed~0|0.0|''|'0'|array{}|false|null (class Bug4117Types\GenericList, argument)", $item);
35+
assertType("T of mixed~(0|0.0|''|'0'|array{}|false|null) (class Bug4117Types\GenericList, argument)", $item);
3636
} else {
3737
assertType("(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|null", $item);
3838
}

tests/PHPStan/Analyser/nsrt/bug-7176.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function test(Suit $x): string {
2525
assertType('Bug7176Types\Suit::Spades', $x);
2626
return 'DOES NOT WORK';
2727
}
28-
assertType('Bug7176Types\Suit~Bug7176Types\Suit::Clubs|Bug7176Types\Suit::Spades', $x);
28+
assertType('Bug7176Types\Suit~(Bug7176Types\Suit::Clubs|Bug7176Types\Suit::Spades)', $x);
2929

3030
return match ($x) {
3131
Suit::Hearts => 'a',

tests/PHPStan/Analyser/nsrt/bug-pr-339.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
assertType('mixed', $a);
1818
assertType('mixed', $c);
1919
if ($a) {
20-
assertType("mixed~0|0.0|''|'0'|array{}|false|null", $a);
20+
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $a);
2121
assertType('mixed', $c);
2222
assertVariableCertainty(TrinaryLogic::createYes(), $a);
2323
}
2424

2525
if ($c) {
2626
assertType('mixed', $a);
27-
assertType("mixed~0|0.0|''|'0'|array{}|false|null", $c);
27+
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $c);
2828
assertVariableCertainty(TrinaryLogic::createYes(), $c);
2929
}
3030
} else {

tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public function sayHello($mixed, int $int, string $string, $numericString, $nonE
1515
if (ctype_digit((string) $mixed)) {
1616
assertType('int<0, max>|numeric-string|true', $mixed);
1717
} else {
18-
assertType('mixed~int<0, max>|numeric-string|true', $mixed);
18+
assertType('mixed~(int<0, max>|numeric-string|true)', $mixed);
1919
}
2020
assertType('mixed', $mixed);
2121

tests/PHPStan/Analyser/nsrt/composer-array-bug.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function doFoo(): void
5151
unset($this->config['authors']);
5252
assertType("array<mixed~'authors', mixed>", $this->config);
5353
} else {
54-
assertType("array&hasOffsetValue('authors', mixed~0|0.0|''|'0'|array{}|false|null)", $this->config);
54+
assertType("array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config);
5555
}
5656

5757
assertType('array', $this->config);

tests/PHPStan/Analyser/nsrt/composer-non-empty-array-after-unset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Foo
1313
public function doFoo()
1414
{
1515
if (!empty($this->config['authors'])) {
16-
assertType("mixed~0|0.0|''|'0'|array{}|false|null", $this->config['authors']);
16+
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $this->config['authors']);
1717
foreach ($this->config['authors'] as $key => $author) {
1818
assertType("mixed", $this->config['authors']);
1919
if (!is_array($author)) {

tests/PHPStan/Analyser/nsrt/ctype-digit.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ public function foo(mixed $foo): void
2020
if (is_int($foo) && ctype_digit($foo)) {
2121
assertType('int<48, 57>|int<256, max>', $foo);
2222
} else {
23-
assertType('mixed~int<48, 57>|int<256, max>', $foo);
23+
assertType('mixed~(int<48, 57>|int<256, max>)', $foo);
2424
}
2525

2626
if (ctype_digit($foo)) {
2727
assertType('int<48, 57>|int<256, max>|numeric-string', $foo);
2828
return;
2929
}
3030

31-
assertType('mixed~int<48, 57>|int<256, max>', $foo); // not all numeric strings are covered by ctype_digit
31+
assertType('mixed~(int<48, 57>|int<256, max>)', $foo); // not all numeric strings are covered by ctype_digit
3232
}
3333
}

tests/PHPStan/Analyser/nsrt/more-types.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function doFoo(
3838
assertType('literal-string&non-empty-string', $nonEmptyLiteralString);
3939
assertType('float|int<min, -1>|int<1, max>|non-falsy-string|true', $nonEmptyScalar);
4040
assertType("0|0.0|''|'0'|false", $emptyScalar);
41-
assertType("mixed~0|0.0|''|'0'|array{}|false|null", $nonEmptyMixed);
41+
assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $nonEmptyMixed);
4242
}
4343

4444
}

tests/PHPStan/Analyser/nsrt/this-subtractable.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public function doFoo()
1212
assertType('$this(ThisSubtractable\Foo)', $this);
1313

1414
if (!$this instanceof Bar && !$this instanceof Baz) {
15-
assertType('$this(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $this);
15+
assertType('$this(ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz))', $this);
1616
} else {
1717
assertType('($this(ThisSubtractable\Foo)&ThisSubtractable\Bar)|($this(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $this);
1818
}
@@ -26,7 +26,7 @@ public function doBar()
2626
assertType('static(ThisSubtractable\Foo)', $s);
2727

2828
if (!$s instanceof Bar && !$s instanceof Baz) {
29-
assertType('static(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $s);
29+
assertType('static(ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz))', $s);
3030
} else {
3131
assertType('(static(ThisSubtractable\Foo)&ThisSubtractable\Bar)|(static(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $s);
3232
}
@@ -52,7 +52,7 @@ public function doBazz(self $s)
5252
assertType('ThisSubtractable\Foo', $s);
5353

5454
if (!$s instanceof Bar && !$s instanceof Baz) {
55-
assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz', $s);
55+
assertType('ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz)', $s);
5656
} else {
5757
assertType('ThisSubtractable\Bar|ThisSubtractable\Baz', $s);
5858
}
@@ -70,12 +70,12 @@ public function doBazzz(self $s)
7070
assertType('ThisSubtractable\Foo&hasMethod(test123)', $s);
7171

7272
if (!$s instanceof Bar && !$s instanceof Baz) {
73-
assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123)', $s);
73+
assertType('ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz)&hasMethod(test123)', $s);
7474
} else {
7575
assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))', $s);
7676
}
7777

78-
assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))|(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123))', $s);
78+
assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))|(ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz)&hasMethod(test123))', $s);
7979
}
8080

8181
/**

tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function (): void {
7676
assertVariableCertainty(TrinaryLogic::createMaybe(), $baz);
7777
assertType('1|2', $baz);
7878
} catch (\Throwable $e) {
79-
assertType('Throwable~InvalidArgumentException|RuntimeException', $e);
79+
assertType('Throwable~(InvalidArgumentException|RuntimeException)', $e);
8080
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
8181
assertVariableCertainty(TrinaryLogic::createYes(), $bar);
8282
assertVariableCertainty(TrinaryLogic::createNo(), $baz);

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,7 +1886,7 @@ public function dataUnion(): iterable
18861886
StaticTypeFactory::truthy(),
18871887
],
18881888
MixedType::class,
1889-
'mixed~0|0.0|\'\'|\'0\'|array{}|false|null=implicit',
1889+
'mixed~(0|0.0|\'\'|\'0\'|array{}|false|null)=implicit',
18901890
],
18911891
[
18921892
[
@@ -3325,7 +3325,7 @@ public function dataIntersect(): iterable
33253325
new MixedType(false, new IntegerType()),
33263326
],
33273327
MixedType::class,
3328-
'mixed~int|string=implicit',
3328+
'mixed~(int|string)=implicit',
33293329
],
33303330
[
33313331
[
@@ -4494,7 +4494,7 @@ public function dataRemove(): array
44944494
StaticTypeFactory::truthy(),
44954495
StaticTypeFactory::falsey(),
44964496
MixedType::class,
4497-
'mixed~0|0.0|\'\'|\'0\'|array{}|false|null',
4497+
'mixed~(0|0.0|\'\'|\'0\'|array{}|false|null)',
44984498
],
44994499
[
45004500
StaticTypeFactory::falsey(),
@@ -4670,7 +4670,7 @@ public function dataRemove(): array
46704670
new MixedType(false, new IntegerType()),
46714671
new StringType(),
46724672
MixedType::class,
4673-
'mixed~int|string',
4673+
'mixed~(int|string)',
46744674
],
46754675
[
46764676
new MixedType(false),
@@ -4706,7 +4706,7 @@ public function dataRemove(): array
47064706
new ObjectType('Exception', new ObjectType('InvalidArgumentException')),
47074707
new ObjectType('LengthException'),
47084708
ObjectType::class,
4709-
'Exception~InvalidArgumentException|LengthException',
4709+
'Exception~(InvalidArgumentException|LengthException)',
47104710
],
47114711
[
47124712
new ObjectType('Exception'),

0 commit comments

Comments
 (0)