diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 6ecc8dd8182..3b7f111cf95 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -256,7 +256,6 @@ public static function doUnion(Type ...$types): Type $alreadyNormalizedCounter = 0; $benevolentTypes = []; - $benevolentUnionObject = null; $neverCount = 0; // transform A | (B | C) to A | B | C for ($i = 0; $i < $typesCount; $i++) { @@ -273,8 +272,8 @@ public static function doUnion(Type ...$types): Type continue; } if ($types[$i] instanceof BenevolentUnionType) { - if ($types[$i] instanceof TemplateBenevolentUnionType && $benevolentUnionObject === null) { - $benevolentUnionObject = $types[$i]; + if ($types[$i] instanceof TemplateType) { + continue; } $benevolentTypesCount = 0; $typesInner = $types[$i]->getTypes(); @@ -528,10 +527,6 @@ public static function doUnion(Type ...$types): Type } if ($tempTypes === []) { - if ($benevolentUnionObject instanceof TemplateBenevolentUnionType) { - return $benevolentUnionObject->withTypes(array_values($types)); - } - return new BenevolentUnionType(array_values($types), true); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10871.php b/tests/PHPStan/Analyser/nsrt/bug-10871.php new file mode 100644 index 00000000000..d898e1eb47c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10871.php @@ -0,0 +1,34 @@ + ...$iterables + * @return self + */ + public function merge(iterable ...$iterables): self; + +} + +/** + * @param Map $map + */ +function test(Map $map, int $int, bool $bool): void +{ + assertType('Bug10871\Map', $map->merge([$int => $bool])); + assertType('Bug10871\Map', $map->merge([$int => $bool], ['test' => new stdClass()])); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-13192.php b/tests/PHPStan/Analyser/nsrt/bug-13192.php new file mode 100644 index 00000000000..35e0c482307 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13192.php @@ -0,0 +1,47 @@ + $items + * @return static + */ + public function merge(self $items): static + { + return $this; + } + +} + +/** + * @param Collection $oranges + * @param Collection $apples + */ +function test(Collection $oranges, Collection $apples): void +{ + assertType('Bug13192\Collection', $oranges->merge($apples)); + assertType('Bug13192\Collection', $apples->merge($oranges)); + assertType('Bug13192\Collection', $oranges->merge($oranges)); + assertType('Bug13192\Collection', $apples->merge($apples)); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-13374.php b/tests/PHPStan/Analyser/nsrt/bug-13374.php new file mode 100644 index 00000000000..563f544b790 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13374.php @@ -0,0 +1,52 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug13374; + +use function PHPStan\Testing\assertType; + +/** + * @template TKey of array-key + * @template TValue + */ +class Collection +{ + + /** + * @template TPushValue + * @param TPushValue $value + * @return $this + * @phpstan-this-out static + */ + public function push(mixed $value): static + { + return $this; + } + + /** + * @template TPutKey of array-key + * @template TPutValue + * @param TPutKey $key + * @param TPutValue $value + * @return $this + * @phpstan-this-out static + */ + public function put(int|string $key, mixed $value): static + { + return $this; + } + +} + +/** + * @param Collection $pushCollection + * @param Collection $putCollection + */ +function test(Collection $pushCollection, Collection $putCollection): void +{ + assertType('Bug13374\Collection', $pushCollection->push(123)); + assertType('Bug13374\Collection', $pushCollection->push('foo')); + assertType('Bug13374\Collection', $putCollection->put(123, 456)); + assertType('Bug13374\Collection', $putCollection->put(789, 'foo')); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-7049.php b/tests/PHPStan/Analyser/nsrt/bug-7049.php new file mode 100644 index 00000000000..89e1b9a9a9c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7049.php @@ -0,0 +1,26 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug7049; + +use Closure; +use function PHPStan\Testing\assertType; + +class Collection +{ + + /** + * @template TGroupKey of array-key + * @param TGroupKey|Closure(mixed): TGroupKey $key + * @return array + */ + public function groupBy(int|string|Closure $key): array + { + return []; + } + +} + +$collection = new Collection(); +assertType("array<'id', Bug7049\Collection>", $collection->groupBy('id')); diff --git a/tests/PHPStan/Analyser/nsrt/bug-7279.php b/tests/PHPStan/Analyser/nsrt/bug-7279.php new file mode 100644 index 00000000000..f716783ac57 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7279.php @@ -0,0 +1,45 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug7279; + +use function PHPStan\Testing\assertType; + +/** + * @template K of array-key + * @template T + * @param array $array + * @param callable(T, K): bool $fn + * @return ($array is non-empty-array ? K|null : null) + */ +function findKey(array $array, callable $fn): string|int|null +{ + foreach ($array as $key => $value) { + if ($fn($value, $key)) { + return $key; + } + } + + return null; +} + +/** + * @param callable(mixed): bool $callback + * @param array $emptyList + * @param array{} $emptyMap + * @param array $unknownList + * @param array{id?: int, name?: string} $unknownMap + * @param non-empty-array $nonEmptyList + * @param array{work: string} $nonEmptyMap + */ +function test(callable $callback, array $emptyList, array $emptyMap, array $unknownList, array $unknownMap, array $nonEmptyList, array $nonEmptyMap): void +{ + assertType('null', findKey([], $callback)); + assertType('null', findKey($emptyList, $callback)); + assertType('null', findKey($emptyMap, $callback)); + assertType('int|null', findKey($unknownList, $callback)); + assertType("'id'|'name'|null", findKey($unknownMap, $callback)); + assertType('int|null', findKey($nonEmptyList, $callback)); + assertType("'work'|null", findKey($nonEmptyMap, $callback)); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-7423.php b/tests/PHPStan/Analyser/nsrt/bug-7423.php new file mode 100644 index 00000000000..7db8ad1208f --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7423.php @@ -0,0 +1,33 @@ + + */ + public function add($key, $value): self + { + return $this; + } + +} + +/** @var ArrayType $type */ +$type = new ArrayType(); + +assertType('Bug7423\ArrayType', $type->add(1, 1)); diff --git a/tests/PHPStan/Analyser/nsrt/bug-8268.php b/tests/PHPStan/Analyser/nsrt/bug-8268.php new file mode 100644 index 00000000000..e0babc7e8cb --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8268.php @@ -0,0 +1,28 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug8268; + +use function PHPStan\Testing\assertType; + +/** + * @template TKey of array-key + */ +class Collection +{ + + /** + * @param TKey|null $offset + */ + public function set(int|string|null $offset): void + { + assertType('TKey of (int|string) (class Bug8268\Collection, argument)|null', $offset); + if ($offset === null) { + return; + } + + assertType('TKey of (int|string) (class Bug8268\Collection, argument)', $offset); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/template-array-key-union-false.php b/tests/PHPStan/Analyser/nsrt/template-array-key-union-false.php new file mode 100644 index 00000000000..5869424a773 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/template-array-key-union-false.php @@ -0,0 +1,35 @@ + $ints + * @param Collection $strings + * @param Collection $keys + */ +function test(Collection $ints, Collection $strings, Collection $keys): void +{ + assertType('int|false', $ints->search('foo')); + assertType('string|false', $strings->search('foo')); + assertType('int|string|false', $keys->search('foo')); +} diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 3cf9ccbb5ca..3f6b1db98ab 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1982,6 +1982,14 @@ public static function dataUnion(): iterable TemplateBenevolentUnionType::class, 'T of (int|string) (function foo(), parameter)', ], + [ + [ + TemplateTypeFactory::create(TemplateTypeScope::createWithFunction('foo'), 'T', new BenevolentUnionType([new IntegerType(), new StringType()]), TemplateTypeVariance::createInvariant()), + new ConstantBooleanType(false), + ], + UnionType::class, + 'T of (int|string) (function foo(), parameter)|false', + ], [ [ new ConstantStringType(''),