diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index dabbc6dca5..fbb7291d0d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -83,6 +83,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/list-type.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2835.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2443.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5508.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10254.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2750.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2850.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2863.php'); diff --git a/tests/PHPStan/Analyser/data/bug-10254.php b/tests/PHPStan/Analyser/data/bug-10254.php new file mode 100644 index 0000000000..3299015ca0 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-10254.php @@ -0,0 +1,91 @@ + + */ + public static function some($value): self + { + return new self($value); + } + + /** + * @template Tu + * + * @param (Closure(T): Tu) $closure + * + * @return Option + */ + public function map(Closure $closure): self + { + return new self($closure($this->unwrap())); + } + + /** + * @return T + */ + public function unwrap() + { + if ($this->value === null) { + throw new RuntimeException(); + } + + return $this->value; + } + + /** + * @template To + * @param self $other + * @return self + */ + public function zip(self $other) + { + return new self([ + $this->unwrap(), + $other->unwrap() + ]); + } +} + + +function (): void { + $value = Option::some(1) + ->zip(Option::some(2)); + + assertType('Bug10254\\Option', $value); + + $value1 = $value->map(function ($value) { + assertType('int', $value[0]); + assertType('int', $value[1]); + return $value[0] + $value[1]; + }); + + assertType('Bug10254\\Option', $value1); + + $value2 = $value->map(function ($value): int { + assertType('int', $value[0]); + assertType('int', $value[1]); + return $value[0] + $value[1]; + }); + + assertType('Bug10254\\Option', $value2); + +}; diff --git a/tests/PHPStan/Analyser/data/bug-5508.php b/tests/PHPStan/Analyser/data/bug-5508.php new file mode 100644 index 0000000000..89af5b4b98 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5508.php @@ -0,0 +1,57 @@ + + */ + protected $items = []; + + /** + * @param array $items + * @return void + */ + public function __construct($items) + { + $this->items = $items; + } + + /** + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback + * @return self + */ + public function map(callable $callback) + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items, $keys); + + return new self(array_combine($keys, $items)); + } + + /** + * @return array + */ + public function all() + { + return $this->items; + } +} + +function (): void { + $result = (new Collection(['book', 'cars']))->map(function($category) { + return $category; + })->all(); + + assertType('array', $result); +}; diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index bb99cc426a..646b96d968 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -276,4 +276,11 @@ public function testBug5592(): void $this->analyse([__DIR__ . '/data/bug-5592.php'], []); } + public function testBug10732(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-10732.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-10732.php b/tests/PHPStan/Rules/Functions/data/bug-10732.php new file mode 100644 index 0000000000..daabd2c88a --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-10732.php @@ -0,0 +1,77 @@ + $items + * @return void + */ + public function __construct(protected array $items = []) {} + + /** + * Run a map over each of the items. + * + * @template TMapValue + * + * @param callable(TValue): TMapValue $callback + * @return static + */ + public function map(callable $callback) + { + return new self(array_map($callback, $this->items)); + } +} + +/** + * I'd expect this to work? + * + * @param Collection> $collection + * @return Collection> + */ +function current(Collection $collection): Collection +{ + return $collection->map(fn(array $item) => $item); +} + +/** + * Removing the Typehint works + * + * @param Collection> $collection + * @return Collection> + */ +function removeTypeHint(Collection $collection): Collection +{ + return $collection->map(fn($item) => $item); +} + +/** + * Typehint works for simple type + * + * @param Collection $collection + * @return Collection + */ +function simplerType(Collection $collection): Collection +{ + return $collection->map(fn(string $item) => $item); +} + +/** + * Typehint works for arrays + * + * @param array> $collection + * @return array> + */ +function useArraysInstead(array $collection): array +{ + return array_map( + fn(array $item) => $item, + $collection, + ); +}