Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions src/Type/TypeCombinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand All @@ -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();
Expand Down Expand Up @@ -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);
}
}
Expand Down
34 changes: 34 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-10871.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types = 1);

namespace Bug10871;

use stdClass;
use function PHPStan\Testing\assertType;

/**
* @template TKey of array-key
* @template TValue
*/
interface Map
{

/**
* @template TOtherKey of array-key
* @template TOtherValue
* @param iterable<TOtherKey, TOtherValue> ...$iterables
* @return self<TKey|TOtherKey, TValue|TOtherValue>
*/
public function merge(iterable ...$iterables): self;

}

/**
* @param Map<string, int> $map
*/
function test(Map $map, int $int, bool $bool): void
{
assertType('Bug10871\Map<int|string, bool|int>', $map->merge([$int => $bool]));
assertType('Bug10871\Map<int|string, bool|int|stdClass>', $map->merge([$int => $bool], ['test' => new stdClass()]));
}
47 changes: 47 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13192.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types = 1);

namespace Bug13192;

use function PHPStan\Testing\assertType;

class Apple
{
}

class Orange
{
}

/**
* @template TKey of array-key
* @template TValue
*/
class Collection
{

/**
* @template UKey of array-key
* @template UValue
* @param static<UKey, UValue> $items
* @return static<TKey|UKey, TValue|UValue>
*/
public function merge(self $items): static
{
return $this;
}

}

/**
* @param Collection<int, Orange> $oranges
* @param Collection<string, Apple> $apples
*/
function test(Collection $oranges, Collection $apples): void
{
assertType('Bug13192\Collection<int|string, Bug13192\Apple|Bug13192\Orange>', $oranges->merge($apples));
assertType('Bug13192\Collection<int|string, Bug13192\Apple|Bug13192\Orange>', $apples->merge($oranges));
assertType('Bug13192\Collection<int, Bug13192\Orange>', $oranges->merge($oranges));
assertType('Bug13192\Collection<string, Bug13192\Apple>', $apples->merge($apples));
}
52 changes: 52 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13374.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php // lint >= 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<int|TKey, TValue|TPushValue>
*/
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<TKey|TPutKey, TValue|TPutValue>
*/
public function put(int|string $key, mixed $value): static
{
return $this;
}

}

/**
* @param Collection<string, int> $pushCollection
* @param Collection<string, int> $putCollection
*/
function test(Collection $pushCollection, Collection $putCollection): void
{
assertType('Bug13374\Collection<int|string, int>', $pushCollection->push(123));
assertType('Bug13374\Collection<int|string, int|string>', $pushCollection->push('foo'));
assertType('Bug13374\Collection<int|string, int>', $putCollection->put(123, 456));
assertType('Bug13374\Collection<int|string, int|string>', $putCollection->put(789, 'foo'));
}
26 changes: 26 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-7049.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php // lint >= 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<TGroupKey, static>
*/
public function groupBy(int|string|Closure $key): array
{
return [];
}

}

$collection = new Collection();
assertType("array<'id', Bug7049\Collection>", $collection->groupBy('id'));
45 changes: 45 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-7279.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug7279;

use function PHPStan\Testing\assertType;

/**
* @template K of array-key
* @template T
* @param array<K, T> $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<never, never> $emptyList
* @param array{} $emptyMap
* @param array<int, string> $unknownList
* @param array{id?: int, name?: string} $unknownMap
* @param non-empty-array<int, string> $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));
}
33 changes: 33 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-7423.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types = 1);

namespace Bug7423;

use function PHPStan\Testing\assertType;

/**
* @template TKey of array-key
* @template TValue
*/
class ArrayType
{

/**
* @template VKey of array-key
* @template V
* @param VKey $key
* @param V $value
* @return self<TKey|VKey, TValue|V>
*/
public function add($key, $value): self
{
return $this;
}

}

/** @var ArrayType<string, string> $type */
$type = new ArrayType();

assertType('Bug7423\ArrayType<int|string, int|string>', $type->add(1, 1));
28 changes: 28 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-8268.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php // lint >= 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);
}

}
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/nsrt/template-array-key-union-false.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace TemplateArrayKeyUnionFalse;

use function PHPStan\Testing\assertType;

/**
* @template TKey of array-key
* @template TValue
*/
class Collection
{

/**
* @param TValue|callable(TValue, TKey): bool $value
* @return TKey|false
*/
public function search($value, bool $strict = false)
{
return false;
}

}

/**
* @param Collection<int, string> $ints
* @param Collection<string, string> $strings
* @param Collection<int|string, string> $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'));
}
8 changes: 8 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(''),
Expand Down
Loading