diff --git a/.github/workflows/types.yml b/.github/workflows/types.yml new file mode 100644 index 000000000000..2b8d7f717ed3 --- /dev/null +++ b/.github/workflows/types.yml @@ -0,0 +1,45 @@ +name: types + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + linux_tests: + runs-on: ubuntu-20.04 + + strategy: + fail-fast: true + matrix: + php: ['8.0'] + stability: [prefer-lowest, prefer-stable] + include: + - php: '8.1' + flags: "--ignore-platform-req=php" + stability: prefer-stable + + name: PHP ${{ matrix.php }} - ${{ matrix.stability }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + + - name: Install dependencies + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress ${{ matrix.flags }} + + - name: Execute type checking + continue-on-error: ${{ matrix.php > 8 }} + run: vendor/bin/phpstan diff --git a/composer.json b/composer.json index 7ce8aa9e8aca..a817bd44d567 100644 --- a/composer.json +++ b/composer.json @@ -86,6 +86,7 @@ "mockery/mockery": "^1.4.2", "orchestra/testbench-core": "^6.23", "pda/pheanstalk": "^4.0", + "phpstan/phpstan": "^0.12.94", "phpunit/phpunit": "^8.5.8|^9.3.3", "predis/predis": "^1.1.2", "symfony/cache": "^5.1.4" diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000000..609d438c7593 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,4 @@ +parameters: + paths: + - types + level: max diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 261a0c856b39..ef927c4f6d7e 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -8,22 +8,32 @@ use IteratorAggregate; use JsonSerializable; +/** + * @template TKey of array-key + * @template TValue + * @template-extends \IteratorAggregate + */ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable { /** * Create a new collection instance if the value isn't one already. * - * @param mixed $items - * @return static + * @template TMakeKey of array-key + * @template TMakeValue + * + * @param iterable $items + * @return static */ public static function make($items = []); /** * Create a new instance by invoking the callback a given amount of times. * + * @template TTimesValue + * * @param int $number - * @param callable|null $callback - * @return static + * @param (callable(int): TTimesValue)|null $callback + * @return static */ public static function times($number, callable $callback = null); @@ -32,37 +42,45 @@ public static function times($number, callable $callback = null); * * @param int $from * @param int $to - * @return static + * @return static */ public static function range($from, $to); /** * Wrap the given value in a collection if applicable. * - * @param mixed $value - * @return static + * @template TWrapValue + * + * @param TWrapValue|iterable $value + * @return static */ public static function wrap($value); /** * Get the underlying items from the given collection if applicable. * - * @param array|static $value - * @return array + * @template TUnwrapKey of array-key + * @template TUnwrapValue + * + * @param array|static $value + * @return array */ public static function unwrap($value); /** * Create a new instance with no items. * - * @return static + * @template TEmptyKey of array-key + * @template TEmptyValue + * + * @return static */ public static function empty(); /** * Get all items in the enumerable. * - * @return array + * @return array */ public function all(); @@ -228,7 +246,7 @@ public function duplicatesStrict($callback = null); /** * Execute a callback over each item. * - * @param callable $callback + * @param callable(TValue): mixed $callback * @return $this */ public function each(callable $callback); @@ -425,9 +443,11 @@ public function whereInstanceOf($type); /** * Get the first item from the enumerable passing the given truth test. * - * @param callable|null $callback - * @param mixed $default - * @return mixed + * @template TFirstDefaultValue + * + * @param (callable(TValue): bool)|null $callback + * @param TFirstDefaultValue $default + * @return TValue|TFirstDefaultValue */ public function first(callable $callback = null, $default = null); @@ -459,9 +479,11 @@ public function flip(); /** * Get an item from the collection by key. * - * @param mixed $key - * @param mixed $default - * @return mixed + * @template TGetDefaultValue + * + * @param TKey $key + * @param TGetDefaultValue $default + * @return TValue|TGetDefaultValue */ public function get($key, $default = null); diff --git a/types/Support/Enumerable.php b/types/Support/Enumerable.php new file mode 100644 index 000000000000..67940bf38a9a --- /dev/null +++ b/types/Support/Enumerable.php @@ -0,0 +1,60 @@ + $enumerable */ +$enumerable = collect([]); +class User extends Authenticatable +{ +} + +foreach ($enumerable as $int => $user) { + assertType('int', $int); + assertType('User', $user); +} + +assertType('Illuminate\Support\Enumerable', $enumerable::make(['string'])); +assertType('Illuminate\Support\Enumerable', $enumerable::make(['string' => new User])); + +assertType('Illuminate\Support\Enumerable', $enumerable::times(10, function ($int) { + // assertType('int', $int); + + return new User; +})); + +assertType('Illuminate\Support\Enumerable', $enumerable->each(function ($user) { + assertType('User', $user); +})); + +assertType('Illuminate\Support\Enumerable', $enumerable->range(1, 100)); + +assertType('Illuminate\Support\Enumerable<(int|string), int>', $enumerable->wrap(1)); +assertType('Illuminate\Support\Enumerable<(int|string), string>', $enumerable->wrap('string')); +assertType('Illuminate\Support\Enumerable<(int|string), string>', $enumerable->wrap(['string'])); +assertType('Illuminate\Support\Enumerable<(int|string), User>', $enumerable->wrap(['string' => new User])); + +assertType('array', $enumerable->unwrap(['string'])); +assertType('array', $enumerable->unwrap( + $enumerable +)); + +assertType('Illuminate\Support\Enumerable<(int|string), mixed>', $enumerable::empty()); + +assertType('array', $enumerable->all()); + +assertType('User|null', $enumerable->get(0)); +assertType('string|User', $enumerable->get(0, 'string')); + +assertType('User|null', $enumerable->first()); +assertType('User|null', $enumerable->first(function ($user) { + assertType('User', $user); + + return true; +})); +assertType('string|User', $enumerable->first(function ($user) { + assertType('User', $user); + + return false; +}, 'string'));