├── src ├── exception │ ├── Exception.php │ └── RuntimeException.php ├── type │ ├── VoidType.php │ ├── NeverType.php │ ├── UnknownType.php │ ├── NullType.php │ ├── MixedType.php │ ├── TrueType.php │ ├── FalseType.php │ ├── GenericObjectType.php │ ├── StaticType.php │ ├── IterableType.php │ ├── ObjectType.php │ ├── SimpleType.php │ ├── IntersectionType.php │ ├── UnionType.php │ ├── CallableType.php │ └── Type.php ├── Parameter.php ├── TypeName.php └── ReflectionMapper.php ├── README.md ├── composer.json ├── LICENSE ├── SECURITY.md └── ChangeLog.md /src/exception/Exception.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use Throwable; 13 | 14 | /** 15 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 16 | */ 17 | interface Exception extends Throwable 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class RuntimeException extends \RuntimeException implements Exception 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/sebastian/type/v)](https://packagist.org/packages/sebastian/type) 2 | [![CI Status](https://github.com/sebastianbergmann/type/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/type/actions) 3 | [![codecov](https://codecov.io/gh/sebastianbergmann/type/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/type) 4 | 5 | # sebastian/type 6 | 7 | Collection of value objects that represent the types of the PHP type system. 8 | 9 | ## Installation 10 | 11 | You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): 12 | 13 | ``` 14 | composer require sebastian/type 15 | ``` 16 | 17 | If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: 18 | 19 | ``` 20 | composer require --dev sebastian/type 21 | ``` 22 | -------------------------------------------------------------------------------- /src/type/VoidType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class VoidType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | return $other instanceof self; 20 | } 21 | 22 | /** 23 | * @return 'void' 24 | */ 25 | public function name(): string 26 | { 27 | return 'void'; 28 | } 29 | 30 | public function allowsNull(): bool 31 | { 32 | return false; 33 | } 34 | 35 | public function isVoid(): bool 36 | { 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/type/NeverType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class NeverType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | return $other instanceof self; 20 | } 21 | 22 | /** 23 | * @return 'never' 24 | */ 25 | public function name(): string 26 | { 27 | return 'never'; 28 | } 29 | 30 | public function allowsNull(): bool 31 | { 32 | return false; 33 | } 34 | 35 | public function isNever(): bool 36 | { 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Parameter.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final readonly class Parameter 16 | { 17 | /** 18 | * @var non-empty-string 19 | */ 20 | private string $name; 21 | private Type $type; 22 | 23 | /** 24 | * @param non-empty-string $name 25 | */ 26 | public function __construct(string $name, Type $type) 27 | { 28 | $this->name = $name; 29 | $this->type = $type; 30 | } 31 | 32 | /** 33 | * @return non-empty-string 34 | */ 35 | public function name(): string 36 | { 37 | return $this->name; 38 | } 39 | 40 | public function type(): Type 41 | { 42 | return $this->type; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/type/UnknownType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class UnknownType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | return true; 20 | } 21 | 22 | /** 23 | * @return 'unknown type' 24 | */ 25 | public function name(): string 26 | { 27 | return 'unknown type'; 28 | } 29 | 30 | /** 31 | * @return '' 32 | */ 33 | public function asString(): string 34 | { 35 | return ''; 36 | } 37 | 38 | public function allowsNull(): bool 39 | { 40 | return true; 41 | } 42 | 43 | public function isUnknown(): bool 44 | { 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/type/NullType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class NullType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | return !($other instanceof VoidType); 20 | } 21 | 22 | /** 23 | * @return 'null' 24 | */ 25 | public function name(): string 26 | { 27 | return 'null'; 28 | } 29 | 30 | /** 31 | * @return 'null' 32 | */ 33 | public function asString(): string 34 | { 35 | return 'null'; 36 | } 37 | 38 | public function allowsNull(): bool 39 | { 40 | return true; 41 | } 42 | 43 | public function isNull(): bool 44 | { 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/type/MixedType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class MixedType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | return !$other instanceof VoidType; 20 | } 21 | 22 | /** 23 | * @return 'mixed' 24 | */ 25 | public function asString(): string 26 | { 27 | return 'mixed'; 28 | } 29 | 30 | /** 31 | * @return 'mixed' 32 | */ 33 | public function name(): string 34 | { 35 | return 'mixed'; 36 | } 37 | 38 | public function allowsNull(): bool 39 | { 40 | return true; 41 | } 42 | 43 | public function isMixed(): bool 44 | { 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/type/TrueType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class TrueType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | if ($other instanceof self) { 20 | return true; 21 | } 22 | 23 | return $other instanceof SimpleType && 24 | $other->name() === 'bool' && 25 | $other->value() === true; 26 | } 27 | 28 | /** 29 | * @return 'true' 30 | */ 31 | public function name(): string 32 | { 33 | return 'true'; 34 | } 35 | 36 | public function allowsNull(): bool 37 | { 38 | return false; 39 | } 40 | 41 | public function isTrue(): bool 42 | { 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/type/FalseType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class FalseType extends Type 16 | { 17 | public function isAssignable(Type $other): bool 18 | { 19 | if ($other instanceof self) { 20 | return true; 21 | } 22 | 23 | return $other instanceof SimpleType && 24 | $other->name() === 'bool' && 25 | $other->value() === false; 26 | } 27 | 28 | /** 29 | * @return 'false' 30 | */ 31 | public function name(): string 32 | { 33 | return 'false'; 34 | } 35 | 36 | public function allowsNull(): bool 37 | { 38 | return false; 39 | } 40 | 41 | public function isFalse(): bool 42 | { 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/type/GenericObjectType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | /** 13 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 14 | */ 15 | final class GenericObjectType extends Type 16 | { 17 | private bool $allowsNull; 18 | 19 | public function __construct(bool $nullable) 20 | { 21 | $this->allowsNull = $nullable; 22 | } 23 | 24 | public function isAssignable(Type $other): bool 25 | { 26 | if ($this->allowsNull && $other instanceof NullType) { 27 | return true; 28 | } 29 | 30 | if (!$other instanceof ObjectType) { 31 | return false; 32 | } 33 | 34 | return true; 35 | } 36 | 37 | /** 38 | * @return 'object' 39 | */ 40 | public function name(): string 41 | { 42 | return 'object'; 43 | } 44 | 45 | public function allowsNull(): bool 46 | { 47 | return $this->allowsNull; 48 | } 49 | 50 | public function isGenericObject(): bool 51 | { 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sebastian/type", 3 | "description": "Collection of value objects that represent the types of the PHP type system", 4 | "type": "library", 5 | "homepage": "https://github.com/sebastianbergmann/type", 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Sebastian Bergmann", 10 | "email": "sebastian@phpunit.de", 11 | "role": "lead" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/sebastianbergmann/type/issues", 16 | "security": "https://github.com/sebastianbergmann/type/security/policy" 17 | }, 18 | "prefer-stable": true, 19 | "require": { 20 | "php": ">=8.3" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^12.0" 24 | }, 25 | "config": { 26 | "platform": { 27 | "php": "8.3.0" 28 | }, 29 | "optimize-autoloader": true, 30 | "sort-packages": true 31 | }, 32 | "autoload": { 33 | "classmap": [ 34 | "src/" 35 | ] 36 | }, 37 | "autoload-dev": { 38 | "classmap": [ 39 | "tests/_fixture" 40 | ], 41 | "files": [ 42 | "tests/_fixture/callback_function.php", 43 | "tests/_fixture/functions_that_declare_return_types.php" 44 | ] 45 | }, 46 | "extra": { 47 | "branch-alias": { 48 | "dev-main": "6.0-dev" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019-2025, Sebastian Bergmann 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/type/StaticType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function is_subclass_of; 13 | use function strcasecmp; 14 | 15 | /** 16 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 17 | */ 18 | final class StaticType extends Type 19 | { 20 | private TypeName $className; 21 | private bool $allowsNull; 22 | 23 | public function __construct(TypeName $className, bool $allowsNull) 24 | { 25 | $this->className = $className; 26 | $this->allowsNull = $allowsNull; 27 | } 28 | 29 | public function isAssignable(Type $other): bool 30 | { 31 | if ($this->allowsNull && $other instanceof NullType) { 32 | return true; 33 | } 34 | 35 | if (!$other instanceof ObjectType) { 36 | return false; 37 | } 38 | 39 | if (0 === strcasecmp($this->className->qualifiedName(), $other->className()->qualifiedName())) { 40 | return true; 41 | } 42 | 43 | if (is_subclass_of($other->className()->qualifiedName(), $this->className->qualifiedName(), true)) { 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * @return 'static' 52 | */ 53 | public function name(): string 54 | { 55 | return 'static'; 56 | } 57 | 58 | public function allowsNull(): bool 59 | { 60 | return $this->allowsNull; 61 | } 62 | 63 | public function isStatic(): bool 64 | { 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/type/IterableType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function assert; 13 | use function class_exists; 14 | use function is_iterable; 15 | use ReflectionClass; 16 | 17 | /** 18 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 19 | */ 20 | final class IterableType extends Type 21 | { 22 | private bool $allowsNull; 23 | 24 | public function __construct(bool $nullable) 25 | { 26 | $this->allowsNull = $nullable; 27 | } 28 | 29 | /** 30 | * @throws RuntimeException 31 | */ 32 | public function isAssignable(Type $other): bool 33 | { 34 | if ($this->allowsNull && $other instanceof NullType) { 35 | return true; 36 | } 37 | 38 | if ($other instanceof self) { 39 | return true; 40 | } 41 | 42 | if ($other instanceof SimpleType) { 43 | return is_iterable($other->value()); 44 | } 45 | 46 | if ($other instanceof ObjectType) { 47 | $className = $other->className()->qualifiedName(); 48 | 49 | assert(class_exists($className)); 50 | 51 | return (new ReflectionClass($className))->isIterable(); 52 | } 53 | 54 | return false; 55 | } 56 | 57 | /** 58 | * @return 'iterable' 59 | */ 60 | public function name(): string 61 | { 62 | return 'iterable'; 63 | } 64 | 65 | public function allowsNull(): bool 66 | { 67 | return $this->allowsNull; 68 | } 69 | 70 | public function isIterable(): bool 71 | { 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/type/ObjectType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function is_subclass_of; 13 | use function strcasecmp; 14 | 15 | /** 16 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 17 | */ 18 | final class ObjectType extends Type 19 | { 20 | private TypeName $className; 21 | private bool $allowsNull; 22 | 23 | public function __construct(TypeName $className, bool $allowsNull) 24 | { 25 | $this->className = $className; 26 | $this->allowsNull = $allowsNull; 27 | } 28 | 29 | public function isAssignable(Type $other): bool 30 | { 31 | if ($this->allowsNull && $other instanceof NullType) { 32 | return true; 33 | } 34 | 35 | if ($other instanceof self) { 36 | if (0 === strcasecmp($this->className->qualifiedName(), $other->className->qualifiedName())) { 37 | return true; 38 | } 39 | 40 | if (is_subclass_of($other->className->qualifiedName(), $this->className->qualifiedName(), true)) { 41 | return true; 42 | } 43 | } 44 | 45 | return false; 46 | } 47 | 48 | /** 49 | * @return non-empty-string 50 | */ 51 | public function name(): string 52 | { 53 | return $this->className->qualifiedName(); 54 | } 55 | 56 | public function allowsNull(): bool 57 | { 58 | return $this->allowsNull; 59 | } 60 | 61 | public function className(): TypeName 62 | { 63 | return $this->className; 64 | } 65 | 66 | public function isObject(): bool 67 | { 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. 4 | 5 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 6 | 7 | Instead, please email `sebastian@phpunit.de`. 8 | 9 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 10 | 11 | * The type of issue 12 | * Full paths of source file(s) related to the manifestation of the issue 13 | * The location of the affected source code (tag/branch/commit or direct URL) 14 | * Any special configuration required to reproduce the issue 15 | * Step-by-step instructions to reproduce the issue 16 | * Proof-of-concept or exploit code (if possible) 17 | * Impact of the issue, including how an attacker might exploit the issue 18 | 19 | This information will help us triage your report more quickly. 20 | 21 | ## Web Context 22 | 23 | The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. 24 | 25 | The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. 26 | 27 | If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. 28 | 29 | Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. 30 | 31 | -------------------------------------------------------------------------------- /src/type/SimpleType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function strtolower; 13 | 14 | /** 15 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 16 | */ 17 | final class SimpleType extends Type 18 | { 19 | /** 20 | * @var non-empty-string 21 | */ 22 | private string $name; 23 | private bool $allowsNull; 24 | private mixed $value; 25 | 26 | /** 27 | * @param non-empty-string $name 28 | */ 29 | public function __construct(string $name, bool $nullable, mixed $value = null) 30 | { 31 | $this->name = $this->normalize($name); 32 | $this->allowsNull = $nullable; 33 | $this->value = $value; 34 | } 35 | 36 | public function isAssignable(Type $other): bool 37 | { 38 | if ($this->allowsNull && $other instanceof NullType) { 39 | return true; 40 | } 41 | 42 | if ($this->name === 'bool' && $other->name() === 'true') { 43 | return true; 44 | } 45 | 46 | if ($this->name === 'bool' && $other->name() === 'false') { 47 | return true; 48 | } 49 | 50 | if ($other instanceof self) { 51 | return $this->name === $other->name; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | /** 58 | * @return non-empty-string 59 | */ 60 | public function name(): string 61 | { 62 | return $this->name; 63 | } 64 | 65 | public function allowsNull(): bool 66 | { 67 | return $this->allowsNull; 68 | } 69 | 70 | public function value(): mixed 71 | { 72 | return $this->value; 73 | } 74 | 75 | public function isSimple(): bool 76 | { 77 | return true; 78 | } 79 | 80 | /** 81 | * @param non-empty-string $name 82 | * 83 | * @return non-empty-string 84 | */ 85 | private function normalize(string $name): string 86 | { 87 | $name = strtolower($name); 88 | 89 | return match ($name) { 90 | 'boolean' => 'bool', 91 | 'real', 'double' => 'float', 92 | 'integer' => 'int', 93 | '[]' => 'array', 94 | default => $name, 95 | }; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/TypeName.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function array_pop; 13 | use function assert; 14 | use function explode; 15 | use function implode; 16 | use function substr; 17 | use ReflectionClass; 18 | 19 | /** 20 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 21 | */ 22 | final readonly class TypeName 23 | { 24 | private ?string $namespaceName; 25 | 26 | /** 27 | * @var non-empty-string 28 | */ 29 | private string $simpleName; 30 | 31 | /** 32 | * @param class-string $fullClassName 33 | */ 34 | public static function fromQualifiedName(string $fullClassName): self 35 | { 36 | if ($fullClassName[0] === '\\') { 37 | $fullClassName = substr($fullClassName, 1); 38 | } 39 | 40 | $classNameParts = explode('\\', $fullClassName); 41 | 42 | $simpleName = array_pop($classNameParts); 43 | $namespaceName = implode('\\', $classNameParts); 44 | 45 | assert($simpleName !== ''); 46 | 47 | return new self($namespaceName, $simpleName); 48 | } 49 | 50 | /** 51 | * @param ReflectionClass $type 52 | */ 53 | public static function fromReflection(ReflectionClass $type): self 54 | { 55 | $simpleName = $type->getShortName(); 56 | 57 | assert($simpleName !== ''); 58 | 59 | return new self( 60 | $type->getNamespaceName(), 61 | $simpleName, 62 | ); 63 | } 64 | 65 | /** 66 | * @param non-empty-string $simpleName 67 | */ 68 | public function __construct(?string $namespaceName, string $simpleName) 69 | { 70 | if ($namespaceName === '') { 71 | $namespaceName = null; 72 | } 73 | 74 | $this->namespaceName = $namespaceName; 75 | $this->simpleName = $simpleName; 76 | } 77 | 78 | public function namespaceName(): ?string 79 | { 80 | return $this->namespaceName; 81 | } 82 | 83 | /** 84 | * @return non-empty-string 85 | */ 86 | public function simpleName(): string 87 | { 88 | return $this->simpleName; 89 | } 90 | 91 | /** 92 | * @return non-empty-string 93 | */ 94 | public function qualifiedName(): string 95 | { 96 | return $this->namespaceName === null 97 | ? $this->simpleName 98 | : $this->namespaceName . '\\' . $this->simpleName; 99 | } 100 | 101 | public function isNamespaced(): bool 102 | { 103 | return $this->namespaceName !== null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/type/IntersectionType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function assert; 13 | use function count; 14 | use function implode; 15 | use function in_array; 16 | use function sort; 17 | 18 | /** 19 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 20 | */ 21 | final class IntersectionType extends Type 22 | { 23 | /** 24 | * @var non-empty-list 25 | */ 26 | private array $types; 27 | 28 | /** 29 | * @throws RuntimeException 30 | */ 31 | public function __construct(Type ...$types) 32 | { 33 | $this->ensureMinimumOfTwoTypes(...$types); 34 | $this->ensureOnlyValidTypes(...$types); 35 | $this->ensureNoDuplicateTypes(...$types); 36 | 37 | assert($types !== []); 38 | 39 | $this->types = $types; 40 | } 41 | 42 | public function isAssignable(Type $other): bool 43 | { 44 | return $other->isObject(); 45 | } 46 | 47 | /** 48 | * @return non-empty-string 49 | */ 50 | public function asString(): string 51 | { 52 | return $this->name(); 53 | } 54 | 55 | /** 56 | * @return non-empty-string 57 | */ 58 | public function name(): string 59 | { 60 | $types = []; 61 | 62 | foreach ($this->types as $type) { 63 | $types[] = $type->name(); 64 | } 65 | 66 | sort($types); 67 | 68 | return implode('&', $types); 69 | } 70 | 71 | public function allowsNull(): bool 72 | { 73 | return false; 74 | } 75 | 76 | public function isIntersection(): bool 77 | { 78 | return true; 79 | } 80 | 81 | /** 82 | * @return non-empty-list 83 | */ 84 | public function types(): array 85 | { 86 | return $this->types; 87 | } 88 | 89 | /** 90 | * @throws RuntimeException 91 | */ 92 | private function ensureMinimumOfTwoTypes(Type ...$types): void 93 | { 94 | if (count($types) < 2) { 95 | throw new RuntimeException( 96 | 'An intersection type must be composed of at least two types', 97 | ); 98 | } 99 | } 100 | 101 | /** 102 | * @throws RuntimeException 103 | */ 104 | private function ensureOnlyValidTypes(Type ...$types): void 105 | { 106 | foreach ($types as $type) { 107 | if (!$type->isObject()) { 108 | throw new RuntimeException( 109 | 'An intersection type can only be composed of interfaces and classes', 110 | ); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * @throws RuntimeException 117 | */ 118 | private function ensureNoDuplicateTypes(Type ...$types): void 119 | { 120 | $names = []; 121 | 122 | foreach ($types as $type) { 123 | assert($type instanceof ObjectType); 124 | 125 | $classQualifiedName = $type->className()->qualifiedName(); 126 | 127 | if (in_array($classQualifiedName, $names, true)) { 128 | throw new RuntimeException('An intersection type must not contain duplicate types'); 129 | } 130 | 131 | $names[] = $classQualifiedName; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/type/UnionType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function assert; 13 | use function count; 14 | use function implode; 15 | use function sort; 16 | 17 | /** 18 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 19 | */ 20 | final class UnionType extends Type 21 | { 22 | /** 23 | * @var non-empty-list 24 | */ 25 | private array $types; 26 | 27 | /** 28 | * @throws RuntimeException 29 | */ 30 | public function __construct(Type ...$types) 31 | { 32 | $this->ensureMinimumOfTwoTypes(...$types); 33 | $this->ensureOnlyValidTypes(...$types); 34 | 35 | assert($types !== []); 36 | 37 | $this->types = $types; 38 | } 39 | 40 | public function isAssignable(Type $other): bool 41 | { 42 | foreach ($this->types as $type) { 43 | if ($type->isAssignable($other)) { 44 | return true; 45 | } 46 | } 47 | 48 | return false; 49 | } 50 | 51 | /** 52 | * @return non-empty-string 53 | */ 54 | public function asString(): string 55 | { 56 | return $this->name(); 57 | } 58 | 59 | /** 60 | * @return non-empty-string 61 | */ 62 | public function name(): string 63 | { 64 | $types = []; 65 | 66 | foreach ($this->types as $type) { 67 | if ($type->isIntersection()) { 68 | $types[] = '(' . $type->name() . ')'; 69 | 70 | continue; 71 | } 72 | 73 | $types[] = $type->name(); 74 | } 75 | 76 | sort($types); 77 | 78 | return implode('|', $types); 79 | } 80 | 81 | public function allowsNull(): bool 82 | { 83 | foreach ($this->types as $type) { 84 | if ($type instanceof NullType) { 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | public function isUnion(): bool 93 | { 94 | return true; 95 | } 96 | 97 | public function containsIntersectionTypes(): bool 98 | { 99 | foreach ($this->types as $type) { 100 | if ($type->isIntersection()) { 101 | return true; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * @return non-empty-list 110 | */ 111 | public function types(): array 112 | { 113 | return $this->types; 114 | } 115 | 116 | /** 117 | * @throws RuntimeException 118 | */ 119 | private function ensureMinimumOfTwoTypes(Type ...$types): void 120 | { 121 | if (count($types) < 2) { 122 | throw new RuntimeException( 123 | 'A union type must be composed of at least two types', 124 | ); 125 | } 126 | } 127 | 128 | /** 129 | * @throws RuntimeException 130 | */ 131 | private function ensureOnlyValidTypes(Type ...$types): void 132 | { 133 | foreach ($types as $type) { 134 | if ($type instanceof UnknownType) { 135 | throw new RuntimeException( 136 | 'A union type must not be composed of an unknown type', 137 | ); 138 | } 139 | 140 | if ($type instanceof VoidType) { 141 | throw new RuntimeException( 142 | 'A union type must not be composed of a void type', 143 | ); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/type/CallableType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function assert; 13 | use function class_exists; 14 | use function count; 15 | use function explode; 16 | use function function_exists; 17 | use function is_array; 18 | use function is_object; 19 | use function is_string; 20 | use function str_contains; 21 | use Closure; 22 | use ReflectionClass; 23 | use ReflectionObject; 24 | 25 | /** 26 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 27 | */ 28 | final class CallableType extends Type 29 | { 30 | private bool $allowsNull; 31 | 32 | public function __construct(bool $nullable) 33 | { 34 | $this->allowsNull = $nullable; 35 | } 36 | 37 | public function isAssignable(Type $other): bool 38 | { 39 | if ($this->allowsNull && $other instanceof NullType) { 40 | return true; 41 | } 42 | 43 | if ($other instanceof self) { 44 | return true; 45 | } 46 | 47 | if ($other instanceof ObjectType) { 48 | if ($this->isClosure($other)) { 49 | return true; 50 | } 51 | 52 | if ($this->hasInvokeMethod($other)) { 53 | return true; 54 | } 55 | } 56 | 57 | if ($other instanceof SimpleType) { 58 | if ($this->isFunction($other)) { 59 | return true; 60 | } 61 | 62 | if ($this->isClassCallback($other)) { 63 | return true; 64 | } 65 | 66 | if ($this->isObjectCallback($other)) { 67 | return true; 68 | } 69 | } 70 | 71 | return false; 72 | } 73 | 74 | /** 75 | * @return 'callable' 76 | */ 77 | public function name(): string 78 | { 79 | return 'callable'; 80 | } 81 | 82 | public function allowsNull(): bool 83 | { 84 | return $this->allowsNull; 85 | } 86 | 87 | public function isCallable(): bool 88 | { 89 | return true; 90 | } 91 | 92 | private function isClosure(ObjectType $type): bool 93 | { 94 | return $type->className()->qualifiedName() === Closure::class; 95 | } 96 | 97 | private function hasInvokeMethod(ObjectType $type): bool 98 | { 99 | $className = $type->className()->qualifiedName(); 100 | 101 | assert(class_exists($className)); 102 | 103 | return (new ReflectionClass($className))->hasMethod('__invoke'); 104 | } 105 | 106 | private function isFunction(SimpleType $type): bool 107 | { 108 | if (!is_string($type->value())) { 109 | return false; 110 | } 111 | 112 | return function_exists($type->value()); 113 | } 114 | 115 | private function isObjectCallback(SimpleType $type): bool 116 | { 117 | if (!is_array($type->value())) { 118 | return false; 119 | } 120 | 121 | if (count($type->value()) !== 2) { 122 | return false; 123 | } 124 | 125 | if (!isset($type->value()[0], $type->value()[1])) { 126 | return false; 127 | } 128 | 129 | if (!is_object($type->value()[0]) || !is_string($type->value()[1])) { 130 | return false; 131 | } 132 | 133 | [$object, $methodName] = $type->value(); 134 | 135 | return (new ReflectionObject($object))->hasMethod($methodName); 136 | } 137 | 138 | private function isClassCallback(SimpleType $type): bool 139 | { 140 | if (!is_string($type->value()) && !is_array($type->value())) { 141 | return false; 142 | } 143 | 144 | if (is_string($type->value())) { 145 | if (!str_contains($type->value(), '::')) { 146 | return false; 147 | } 148 | 149 | [$className, $methodName] = explode('::', $type->value()); 150 | } 151 | 152 | if (is_array($type->value())) { 153 | if (count($type->value()) !== 2) { 154 | return false; 155 | } 156 | 157 | if (!isset($type->value()[0], $type->value()[1])) { 158 | return false; 159 | } 160 | 161 | if (!is_string($type->value()[0]) || !is_string($type->value()[1])) { 162 | return false; 163 | } 164 | 165 | [$className, $methodName] = $type->value(); 166 | } 167 | 168 | if (!class_exists($className)) { 169 | return false; 170 | } 171 | 172 | $class = new ReflectionClass($className); 173 | 174 | if (!$class->hasMethod($methodName)) { 175 | return false; 176 | } 177 | 178 | $method = $class->getMethod($methodName); 179 | 180 | return $method->isPublic() && $method->isStatic(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/type/Type.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function gettype; 13 | use function is_object; 14 | use function strtolower; 15 | 16 | /** 17 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 18 | */ 19 | abstract class Type 20 | { 21 | public static function fromValue(mixed $value, bool $allowsNull): self 22 | { 23 | if ($allowsNull === false) { 24 | if ($value === true) { 25 | return new TrueType; 26 | } 27 | 28 | if ($value === false) { 29 | return new FalseType; 30 | } 31 | } 32 | 33 | $typeName = gettype($value); 34 | 35 | if (is_object($value)) { 36 | return new ObjectType(TypeName::fromQualifiedName($value::class), $allowsNull); 37 | } 38 | 39 | $type = self::fromName($typeName, $allowsNull); 40 | 41 | if ($type instanceof SimpleType) { 42 | $type = new SimpleType($typeName, $allowsNull, $value); 43 | } 44 | 45 | return $type; 46 | } 47 | 48 | /** 49 | * @param non-empty-string $typeName 50 | */ 51 | public static function fromName(string $typeName, bool $allowsNull): self 52 | { 53 | return match (strtolower($typeName)) { 54 | 'callable' => new CallableType($allowsNull), 55 | 'true' => new TrueType, 56 | 'false' => new FalseType, 57 | 'iterable' => new IterableType($allowsNull), 58 | 'never' => new NeverType, 59 | 'null' => new NullType, 60 | 'object' => new GenericObjectType($allowsNull), 61 | 'unknown type' => new UnknownType, 62 | 'void' => new VoidType, 63 | 'array', 'bool', 'boolean', 'double', 'float', 'int', 'integer', 'real', 'resource', 'resource (closed)', 'string' => new SimpleType($typeName, $allowsNull), 64 | 'mixed' => new MixedType, 65 | /** @phpstan-ignore argument.type */ 66 | default => new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull), 67 | }; 68 | } 69 | 70 | public function asString(): string 71 | { 72 | return ($this->allowsNull() ? '?' : '') . $this->name(); 73 | } 74 | 75 | /** 76 | * @phpstan-assert-if-true CallableType $this 77 | */ 78 | public function isCallable(): bool 79 | { 80 | return false; 81 | } 82 | 83 | /** 84 | * @phpstan-assert-if-true TrueType $this 85 | */ 86 | public function isTrue(): bool 87 | { 88 | return false; 89 | } 90 | 91 | /** 92 | * @phpstan-assert-if-true FalseType $this 93 | */ 94 | public function isFalse(): bool 95 | { 96 | return false; 97 | } 98 | 99 | /** 100 | * @phpstan-assert-if-true GenericObjectType $this 101 | */ 102 | public function isGenericObject(): bool 103 | { 104 | return false; 105 | } 106 | 107 | /** 108 | * @phpstan-assert-if-true IntersectionType $this 109 | */ 110 | public function isIntersection(): bool 111 | { 112 | return false; 113 | } 114 | 115 | /** 116 | * @phpstan-assert-if-true IterableType $this 117 | */ 118 | public function isIterable(): bool 119 | { 120 | return false; 121 | } 122 | 123 | /** 124 | * @phpstan-assert-if-true MixedType $this 125 | */ 126 | public function isMixed(): bool 127 | { 128 | return false; 129 | } 130 | 131 | /** 132 | * @phpstan-assert-if-true NeverType $this 133 | */ 134 | public function isNever(): bool 135 | { 136 | return false; 137 | } 138 | 139 | /** 140 | * @phpstan-assert-if-true NullType $this 141 | */ 142 | public function isNull(): bool 143 | { 144 | return false; 145 | } 146 | 147 | /** 148 | * @phpstan-assert-if-true ObjectType $this 149 | */ 150 | public function isObject(): bool 151 | { 152 | return false; 153 | } 154 | 155 | /** 156 | * @phpstan-assert-if-true SimpleType $this 157 | */ 158 | public function isSimple(): bool 159 | { 160 | return false; 161 | } 162 | 163 | /** 164 | * @phpstan-assert-if-true StaticType $this 165 | */ 166 | public function isStatic(): bool 167 | { 168 | return false; 169 | } 170 | 171 | /** 172 | * @phpstan-assert-if-true UnionType $this 173 | */ 174 | public function isUnion(): bool 175 | { 176 | return false; 177 | } 178 | 179 | /** 180 | * @phpstan-assert-if-true UnknownType $this 181 | */ 182 | public function isUnknown(): bool 183 | { 184 | return false; 185 | } 186 | 187 | /** 188 | * @phpstan-assert-if-true VoidType $this 189 | */ 190 | public function isVoid(): bool 191 | { 192 | return false; 193 | } 194 | 195 | abstract public function isAssignable(self $other): bool; 196 | 197 | /** 198 | * @return non-empty-string 199 | */ 200 | abstract public function name(): string; 201 | 202 | abstract public function allowsNull(): bool; 203 | } 204 | -------------------------------------------------------------------------------- /src/ReflectionMapper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace SebastianBergmann\Type; 11 | 12 | use function array_filter; 13 | use function assert; 14 | use ReflectionFunction; 15 | use ReflectionIntersectionType; 16 | use ReflectionMethod; 17 | use ReflectionNamedType; 18 | use ReflectionProperty; 19 | use ReflectionType; 20 | use ReflectionUnionType; 21 | 22 | /** 23 | * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library 24 | */ 25 | final class ReflectionMapper 26 | { 27 | /** 28 | * @return list 29 | */ 30 | public function fromParameterTypes(ReflectionFunction|ReflectionMethod $reflector): array 31 | { 32 | $parameters = []; 33 | 34 | foreach ($reflector->getParameters() as $parameter) { 35 | $name = $parameter->getName(); 36 | 37 | if (!$parameter->hasType()) { 38 | $parameters[] = new Parameter($name, new UnknownType); 39 | 40 | continue; 41 | } 42 | 43 | $type = $parameter->getType(); 44 | 45 | if ($type instanceof ReflectionNamedType) { 46 | $parameters[] = new Parameter( 47 | $name, 48 | $this->mapNamedType($type, $reflector), 49 | ); 50 | 51 | continue; 52 | } 53 | 54 | if ($type instanceof ReflectionUnionType) { 55 | $parameters[] = new Parameter( 56 | $name, 57 | $this->mapUnionType($type, $reflector), 58 | ); 59 | 60 | continue; 61 | } 62 | 63 | if ($type instanceof ReflectionIntersectionType) { 64 | $parameters[] = new Parameter( 65 | $name, 66 | $this->mapIntersectionType($type, $reflector), 67 | ); 68 | } 69 | } 70 | 71 | return $parameters; 72 | } 73 | 74 | public function fromReturnType(ReflectionFunction|ReflectionMethod $reflector): Type 75 | { 76 | if (!$this->hasReturnType($reflector)) { 77 | return new UnknownType; 78 | } 79 | 80 | $returnType = $this->returnType($reflector); 81 | 82 | assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType || $returnType instanceof ReflectionIntersectionType); 83 | 84 | if ($returnType instanceof ReflectionNamedType) { 85 | return $this->mapNamedType($returnType, $reflector); 86 | } 87 | 88 | if ($returnType instanceof ReflectionUnionType) { 89 | return $this->mapUnionType($returnType, $reflector); 90 | } 91 | 92 | return $this->mapIntersectionType($returnType, $reflector); 93 | } 94 | 95 | public function fromPropertyType(ReflectionProperty $reflector): Type 96 | { 97 | if (!$reflector->hasType()) { 98 | return new UnknownType; 99 | } 100 | 101 | $propertyType = $reflector->getType(); 102 | 103 | assert($propertyType instanceof ReflectionNamedType || $propertyType instanceof ReflectionUnionType || $propertyType instanceof ReflectionIntersectionType); 104 | 105 | if ($propertyType instanceof ReflectionNamedType) { 106 | return $this->mapNamedType($propertyType, $reflector); 107 | } 108 | 109 | if ($propertyType instanceof ReflectionUnionType) { 110 | return $this->mapUnionType($propertyType, $reflector); 111 | } 112 | 113 | return $this->mapIntersectionType($propertyType, $reflector); 114 | } 115 | 116 | private function mapNamedType(ReflectionNamedType $type, ReflectionFunction|ReflectionMethod|ReflectionProperty $reflector): Type 117 | { 118 | $classScope = !$reflector instanceof ReflectionFunction; 119 | $typeName = $type->getName(); 120 | 121 | assert($typeName !== ''); 122 | 123 | if ($classScope && $typeName === 'self') { 124 | return ObjectType::fromName( 125 | $reflector->getDeclaringClass()->getName(), 126 | $type->allowsNull(), 127 | ); 128 | } 129 | 130 | if ($classScope && $typeName === 'static') { 131 | return new StaticType( 132 | TypeName::fromReflection($reflector->getDeclaringClass()), 133 | $type->allowsNull(), 134 | ); 135 | } 136 | 137 | if ($typeName === 'mixed') { 138 | return new MixedType; 139 | } 140 | 141 | if ($classScope && $typeName === 'parent') { 142 | $parentClass = $reflector->getDeclaringClass()->getParentClass(); 143 | 144 | assert($parentClass !== false); 145 | 146 | return ObjectType::fromName( 147 | $parentClass->getName(), 148 | $type->allowsNull(), 149 | ); 150 | } 151 | 152 | return Type::fromName( 153 | $typeName, 154 | $type->allowsNull(), 155 | ); 156 | } 157 | 158 | private function mapUnionType(ReflectionUnionType $type, ReflectionFunction|ReflectionMethod|ReflectionProperty $reflector): Type 159 | { 160 | $types = []; 161 | $objectType = false; 162 | $genericObjectType = false; 163 | 164 | foreach ($type->getTypes() as $_type) { 165 | if ($_type instanceof ReflectionNamedType) { 166 | $namedType = $this->mapNamedType($_type, $reflector); 167 | 168 | if ($namedType instanceof GenericObjectType) { 169 | $genericObjectType = true; 170 | } elseif ($namedType instanceof ObjectType) { 171 | $objectType = true; 172 | } 173 | 174 | $types[] = $namedType; 175 | 176 | continue; 177 | } 178 | 179 | $types[] = $this->mapIntersectionType($_type, $reflector); 180 | } 181 | 182 | if ($objectType && $genericObjectType) { 183 | $types = array_filter( 184 | $types, 185 | static function (Type $type): bool 186 | { 187 | if ($type instanceof ObjectType) { 188 | return false; 189 | } 190 | 191 | return true; 192 | }, 193 | ); 194 | } 195 | 196 | return new UnionType(...$types); 197 | } 198 | 199 | private function mapIntersectionType(ReflectionIntersectionType $type, ReflectionFunction|ReflectionMethod|ReflectionProperty $reflector): Type 200 | { 201 | $types = []; 202 | 203 | foreach ($type->getTypes() as $_type) { 204 | assert($_type instanceof ReflectionNamedType); 205 | 206 | $types[] = $this->mapNamedType($_type, $reflector); 207 | } 208 | 209 | return new IntersectionType(...$types); 210 | } 211 | 212 | private function hasReturnType(ReflectionFunction|ReflectionMethod $reflector): bool 213 | { 214 | if ($reflector->hasReturnType()) { 215 | return true; 216 | } 217 | 218 | return $reflector->hasTentativeReturnType(); 219 | } 220 | 221 | private function returnType(ReflectionFunction|ReflectionMethod $reflector): ?ReflectionType 222 | { 223 | if ($reflector->hasReturnType()) { 224 | return $reflector->getReturnType(); 225 | } 226 | 227 | return $reflector->getTentativeReturnType(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 4 | 5 | ## [6.0.3] - 2025-08-09 6 | 7 | ### Fixed 8 | 9 | * [#34](https://github.com/sebastianbergmann/type/pull/34): `infection.json` is missing from `.gitattributes` 10 | 11 | ## [6.0.2] - 2025-03-18 12 | 13 | ### Fixed 14 | 15 | * [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly 16 | 17 | ## [6.0.1] - 2025-03-18 18 | 19 | ### Fixed 20 | 21 | * [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly 22 | 23 | ## [6.0.0] - 2025-02-07 24 | 25 | ### Removed 26 | 27 | * This component is no longer supported on PHP 8.2 28 | 29 | ## [5.1.3] - 2025-08-09 30 | 31 | ### Fixed 32 | 33 | * [#34](https://github.com/sebastianbergmann/type/pull/34): `infection.json` is missing from `.gitattributes` 34 | 35 | ## [5.1.2] - 2025-03-18 36 | 37 | ### Fixed 38 | 39 | * [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly 40 | 41 | ## [5.1.1] - 2025-03-18 42 | 43 | ### Fixed 44 | 45 | * [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly 46 | 47 | ## [5.1.0] - 2024-09-17 48 | 49 | ### Added 50 | 51 | * Added `ReflectionMapper::fromPropertyType()` for mapping `\ReflectionProperty` to a `Type` object 52 | 53 | ## [5.0.1] - 2024-07-03 54 | 55 | ### Changed 56 | 57 | * This project now uses PHPStan instead of Psalm for static analysis 58 | 59 | ## [5.0.0] - 2024-02-02 60 | 61 | ### Removed 62 | 63 | * This component is no longer supported on PHP 8.1 64 | 65 | ## [4.0.0] - 2023-02-03 66 | 67 | ### Removed 68 | 69 | * This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 70 | 71 | ## [3.2.1] - 2023-02-03 72 | 73 | ### Fixed 74 | 75 | * [#28](https://github.com/sebastianbergmann/type/pull/28): Potential undefined offset warning/notice 76 | 77 | ## [3.2.0] - 2022-09-12 78 | 79 | ### Added 80 | 81 | * [#25](https://github.com/sebastianbergmann/type/issues/25): Support Disjunctive Normal Form types 82 | * Added `ReflectionMapper::fromParameterTypes()` 83 | * Added `IntersectionType::types()` and `UnionType::types()` 84 | * Added `UnionType::containsIntersectionTypes()` 85 | 86 | ## [3.1.0] - 2022-08-29 87 | 88 | ### Added 89 | 90 | * [#21](https://github.com/sebastianbergmann/type/issues/21): Support `true` as stand-alone type 91 | 92 | ## [3.0.0] - 2022-03-15 93 | 94 | ### Added 95 | 96 | * Support for intersection types introduced in PHP 8.1 97 | * Support for the `never` return type introduced in PHP 8.1 98 | * Added `Type::isCallable()`, `Type::isGenericObject()`, `Type::isIterable()`, `Type::isMixed()`, `Type::isNever()`, `Type::isNull()`, `Type::isObject()`, `Type::isSimple()`, `Type::isStatic()`, `Type::isUnion()`, `Type::isUnknown()`, and `Type::isVoid()` 99 | 100 | ### Changed 101 | 102 | * Renamed `ReflectionMapper::fromMethodReturnType(ReflectionMethod $method)` to `ReflectionMapper::fromReturnType(ReflectionFunctionAbstract $functionOrMethod)` 103 | 104 | ### Removed 105 | 106 | * Removed `Type::getReturnTypeDeclaration()` (use `Type::asString()` instead and prefix its result with `': '`) 107 | * Removed `TypeName::getNamespaceName()` (use `TypeName::namespaceName()` instead) 108 | * Removed `TypeName::getSimpleName()` (use `TypeName::simpleName()` instead) 109 | * Removed `TypeName::getQualifiedName()` (use `TypeName::qualifiedName()` instead) 110 | 111 | ## [2.3.4] - 2021-06-15 112 | 113 | ### Fixed 114 | 115 | * Fixed regression introduced in 2.3.3 116 | 117 | ## [2.3.3] - 2021-06-15 [YANKED] 118 | 119 | ### Fixed 120 | 121 | * [#15](https://github.com/sebastianbergmann/type/issues/15): "false" pseudo type is not handled properly 122 | 123 | ## [2.3.2] - 2021-06-04 124 | 125 | ### Fixed 126 | 127 | * Fixed handling of tentatively declared return types 128 | 129 | ## [2.3.1] - 2020-10-26 130 | 131 | ### Fixed 132 | 133 | * `SebastianBergmann\Type\Exception` now correctly extends `\Throwable` 134 | 135 | ## [2.3.0] - 2020-10-06 136 | 137 | ### Added 138 | 139 | * [#14](https://github.com/sebastianbergmann/type/issues/14): Support for `static` return type that is introduced in PHP 8 140 | 141 | ## [2.2.2] - 2020-09-28 142 | 143 | ### Changed 144 | 145 | * Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` 146 | 147 | ## [2.2.1] - 2020-07-05 148 | 149 | ### Fixed 150 | 151 | * Fixed handling of `mixed` type in `ReflectionMapper::fromMethodReturnType()` 152 | 153 | ## [2.2.0] - 2020-07-05 154 | 155 | ### Added 156 | 157 | * Added `MixedType` object for representing PHP 8's `mixed` type 158 | 159 | ## [2.1.1] - 2020-06-26 160 | 161 | ### Added 162 | 163 | * This component is now supported on PHP 8 164 | 165 | ## [2.1.0] - 2020-06-01 166 | 167 | ### Added 168 | 169 | * Added `UnionType` object for representing PHP 8's Union Types 170 | * Added `ReflectionMapper::fromMethodReturnType()` for mapping `\ReflectionMethod::getReturnType()` to a `Type` object 171 | * Added `Type::name()` for retrieving the name of a type 172 | * Added `Type::asString()` for retrieving a textual representation of a type 173 | 174 | ### Changed 175 | 176 | * Deprecated `Type::getReturnTypeDeclaration()` (use `Type::asString()` instead and prefix its result with `': '`) 177 | * Deprecated `TypeName::getNamespaceName()` (use `TypeName::namespaceName()` instead) 178 | * Deprecated `TypeName::getSimpleName()` (use `TypeName::simpleName()` instead) 179 | * Deprecated `TypeName::getQualifiedName()` (use `TypeName::qualifiedName()` instead) 180 | 181 | ## [2.0.0] - 2020-02-07 182 | 183 | ### Removed 184 | 185 | * This component is no longer supported on PHP 7.2 186 | 187 | ## [1.1.3] - 2019-07-02 188 | 189 | ### Fixed 190 | 191 | * Fixed class name comparison in `ObjectType` to be case-insensitive 192 | 193 | ## [1.1.2] - 2019-06-19 194 | 195 | ### Fixed 196 | 197 | * Fixed handling of `object` type 198 | 199 | ## [1.1.1] - 2019-06-08 200 | 201 | ### Fixed 202 | 203 | * Fixed autoloading of `callback_function.php` fixture file 204 | 205 | ## [1.1.0] - 2019-06-07 206 | 207 | ### Added 208 | 209 | * Added support for `callable` type 210 | * Added support for `iterable` type 211 | 212 | ## [1.0.0] - 2019-06-06 213 | 214 | * Initial release based on [code contributed by Michel Hartmann to PHPUnit](https://github.com/sebastianbergmann/phpunit/pull/3673) 215 | 216 | [6.0.3]: https://github.com/sebastianbergmann/type/compare/6.0.2...6.0.3 217 | [6.0.2]: https://github.com/sebastianbergmann/type/compare/6.0.1...6.0.2 218 | [6.0.1]: https://github.com/sebastianbergmann/type/compare/6.0.0...6.0.1 219 | [6.0.0]: https://github.com/sebastianbergmann/type/compare/5.1...6.0.0 220 | [5.1.3]: https://github.com/sebastianbergmann/type/compare/5.1.2...5.1.3 221 | [5.1.2]: https://github.com/sebastianbergmann/type/compare/5.1.1...5.1.2 222 | [5.1.1]: https://github.com/sebastianbergmann/type/compare/5.1.0...5.1.1 223 | [5.1.0]: https://github.com/sebastianbergmann/type/compare/5.0.1...5.1.0 224 | [5.0.1]: https://github.com/sebastianbergmann/type/compare/5.0.0...5.0.1 225 | [5.0.0]: https://github.com/sebastianbergmann/type/compare/4.0...5.0.0 226 | [4.0.0]: https://github.com/sebastianbergmann/type/compare/3.2.1...4.0.0 227 | [3.2.1]: https://github.com/sebastianbergmann/type/compare/3.2.0...3.2.1 228 | [3.2.0]: https://github.com/sebastianbergmann/type/compare/3.1.0...3.2.0 229 | [3.1.0]: https://github.com/sebastianbergmann/type/compare/3.0.0...3.1.0 230 | [3.0.0]: https://github.com/sebastianbergmann/type/compare/2.3.4...3.0.0 231 | [2.3.4]: https://github.com/sebastianbergmann/type/compare/ca39369c41313ed12c071ed38ecda8fcdb248859...2.3.4 232 | [2.3.3]: https://github.com/sebastianbergmann/type/compare/2.3.2...ca39369c41313ed12c071ed38ecda8fcdb248859 233 | [2.3.2]: https://github.com/sebastianbergmann/type/compare/2.3.1...2.3.2 234 | [2.3.1]: https://github.com/sebastianbergmann/type/compare/2.3.0...2.3.1 235 | [2.3.0]: https://github.com/sebastianbergmann/type/compare/2.2.2...2.3.0 236 | [2.2.2]: https://github.com/sebastianbergmann/type/compare/2.2.1...2.2.2 237 | [2.2.1]: https://github.com/sebastianbergmann/type/compare/2.2.0...2.2.1 238 | [2.2.0]: https://github.com/sebastianbergmann/type/compare/2.1.1...2.2.0 239 | [2.1.1]: https://github.com/sebastianbergmann/type/compare/2.1.0...2.1.1 240 | [2.1.0]: https://github.com/sebastianbergmann/type/compare/2.0.0...2.1.0 241 | [2.0.0]: https://github.com/sebastianbergmann/type/compare/1.1.3...2.0.0 242 | [1.1.3]: https://github.com/sebastianbergmann/type/compare/1.1.2...1.1.3 243 | [1.1.2]: https://github.com/sebastianbergmann/type/compare/1.1.1...1.1.2 244 | [1.1.1]: https://github.com/sebastianbergmann/type/compare/1.1.0...1.1.1 245 | [1.1.0]: https://github.com/sebastianbergmann/type/compare/1.0.0...1.1.0 246 | [1.0.0]: https://github.com/sebastianbergmann/type/compare/ff74aa41746bd8d10e931843ebf37d42da513ede...1.0.0 247 | --------------------------------------------------------------------------------