From 90661998018ecebccf4bbd933bd7cbe86b3e3cb8 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 4 Nov 2022 17:21:07 +0100 Subject: [PATCH 1/2] fix phpbench --- Makefile | 2 +- phive.xml | 4 -- phpbench.json | 6 ++- tests/benchmark/ContextFactoryBench.php | 8 +--- tests/benchmark/TypeResolverBench.php | 37 +------------------ .../TypeResolverWithContextBench.php | 15 +------- 6 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 phive.xml diff --git a/Makefile b/Makefile index 9b28eb9..4c6372f 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ vendor: composer.json composer.lock .PHONY: benchmark benchmark: - docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project php:7.4-cli tools/phpbench run + docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project php:7.4-cli vendor/bin/phpbench run .PHONY: rector rector: ## Refactor code using rector diff --git a/phive.xml b/phive.xml deleted file mode 100644 index 5be7457..0000000 --- a/phive.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/phpbench.json b/phpbench.json index 0b8d58b..0e5750e 100644 --- a/phpbench.json +++ b/phpbench.json @@ -1,4 +1,6 @@ { - "bootstrap": "vendor/autoload.php", - "path": "tests/benchmark" + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/benchmark", + "runner.file_pattern": "*Bench.php" } diff --git a/tests/benchmark/ContextFactoryBench.php b/tests/benchmark/ContextFactoryBench.php index bc28065..b19cb8e 100644 --- a/tests/benchmark/ContextFactoryBench.php +++ b/tests/benchmark/ContextFactoryBench.php @@ -4,6 +4,7 @@ namespace benchmark; +use PhpBench\Benchmark\Metadata\Annotations\Warmup; use phpDocumentor\Reflection\Types\ContextFactory; /** @@ -20,13 +21,6 @@ public function setup() /** * @Warmup(1) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 120Mb", "title"="memory peak"}, - * "main.wall_time < 3S" - * } - * ) */ public function benchCreateContextForNamespace() { diff --git a/tests/benchmark/TypeResolverBench.php b/tests/benchmark/TypeResolverBench.php index dc91903..5f823d5 100644 --- a/tests/benchmark/TypeResolverBench.php +++ b/tests/benchmark/TypeResolverBench.php @@ -4,6 +4,8 @@ namespace benchmark; +use PhpBench\Benchmark\Metadata\Annotations\Revs; +use PhpBench\Benchmark\Metadata\Annotations\Warmup; use phpDocumentor\Reflection\TypeResolver; /** @@ -21,13 +23,6 @@ public function setup() /** * @Warmup(2) * @Revs(10000) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 300us" - * } - * ) */ public function benchResolveSingleType() : void { @@ -37,13 +32,6 @@ public function benchResolveSingleType() : void /** * @Warmup(2) * @Revs(10000) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 0.5ms" - * } - * ) */ public function benchResolveCompoundType() : void { @@ -53,13 +41,6 @@ public function benchResolveCompoundType() : void /** * @Warmup(2) * @Revs(10000) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 300us" - * } - * ) */ public function benchResolveArrayType() : void { @@ -69,13 +50,6 @@ public function benchResolveArrayType() : void /** * @Warmup(2) * @Revs(10000) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 300us" - * } - * ) */ public function benchResolveCompoundArrayType() : void { @@ -85,13 +59,6 @@ public function benchResolveCompoundArrayType() : void /** * @Warmup(2) * @Revs(10000) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 1ms" - * } - * ) */ public function benchResolveCompoundArrayWithDefinedTypes() : void { diff --git a/tests/benchmark/TypeResolverWithContextBench.php b/tests/benchmark/TypeResolverWithContextBench.php index 49626e8..ee1163f 100644 --- a/tests/benchmark/TypeResolverWithContextBench.php +++ b/tests/benchmark/TypeResolverWithContextBench.php @@ -4,6 +4,7 @@ namespace benchmark; +use PhpBench\Benchmark\Metadata\Annotations\Warmup; use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\ContextFactory; @@ -32,13 +33,6 @@ public function setup() /** * @Warmup(2) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 1ms" - * } - * ) */ public function benchResolveCompoundArrayWithDefinedTypes() : void { @@ -47,13 +41,6 @@ public function benchResolveCompoundArrayWithDefinedTypes() : void /** * @Warmup(2) - * @Executor( - * "blackfire", - * assertions={ - * {"expression"="main.peak_memory < 11kb", "title"="memory peak"}, - * "main.wall_time < 1ms" - * } - * ) */ public function benchArrayOfClass() : void { From 962f827dc3fdc3a5ee362ead9721c1efe8c29605 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 11 Nov 2022 09:15:08 +0100 Subject: [PATCH 2/2] Full rewrite of type resolving Types are no longer parsed by this library. We do now use the phpstan parser to parse types. Having support for more advanced type definitions without the burden to support all parser logic. In this commit I do introduce support for: - array shapes - constant expressions like integers, strings and floats - constant expressions for enums and class constants - Callable support with and without params and return types. A new set of unittests have been added to ensure everything keeps working as expected. --- composer.json | 6 +- composer.lock | 834 +++++++++++++++++++++++- src/PseudoTypes/ArrayShape.php | 44 ++ src/PseudoTypes/ArrayShapeItem.php | 62 ++ src/PseudoTypes/ConstExpression.php | 54 ++ src/PseudoTypes/FloatValue.php | 44 ++ src/PseudoTypes/IntegerValue.php | 44 ++ src/PseudoTypes/StringValue.php | 46 ++ src/TypeResolver.php | 634 +++++++----------- src/Types/AggregatedType.php | 2 +- src/Types/CallableParameter.php | 72 ++ src/Types/Callable_.php | 24 + tests/unit/CollectionResolverTest.php | 22 +- tests/unit/IntegerRangeResolverTest.php | 24 +- tests/unit/TypeResolverTest.php | 250 ++++++- tests/unit/Types/CompoundTest.php | 30 + tests/unit/Types/ContextFactoryTest.php | 2 +- 17 files changed, 1730 insertions(+), 464 deletions(-) create mode 100644 src/PseudoTypes/ArrayShape.php create mode 100644 src/PseudoTypes/ArrayShapeItem.php create mode 100644 src/PseudoTypes/ConstExpression.php create mode 100644 src/PseudoTypes/FloatValue.php create mode 100644 src/PseudoTypes/IntegerValue.php create mode 100644 src/PseudoTypes/StringValue.php create mode 100644 src/Types/CallableParameter.php diff --git a/composer.json b/composer.json index a7ae10f..e69777c 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ ], "require": { "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" }, "require-dev": { "ext-tokenizer": "*", @@ -20,7 +21,8 @@ "phpstan/phpstan-phpunit": "^1.1", "phpstan/extension-installer": "^1.1", "vimeo/psalm": "^4.25", - "rector/rector": "^0.13.9" + "rector/rector": "^0.13.9", + "phpbench/phpbench": "^1.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index adcb69d..24293cd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0995b56862f68800c3c014da7cd2503f", + "content-hash": "e012cde7a3d864a1bed7eea7698cf2eb", "packages": [ { "name": "phpdocumentor/reflection-common", @@ -58,6 +58,51 @@ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "33aefcdab42900e36366d0feab6206e2dd68f947" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/33aefcdab42900e36366d0feab6206e2dd68f947", + "reference": "33aefcdab42900e36366d0feab6206e2dd68f947", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.0" + }, + "time": "2022-10-21T09:57:39+00:00" } ], "packages-dev": [ @@ -555,6 +600,79 @@ }, "time": "2019-12-04T15:06:13+00:00" }, + { + "name": "doctrine/annotations", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.3" + }, + "time": "2022-07-02T10:48:51+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.1", @@ -625,6 +743,82 @@ ], "time": "2022-03-03T08:28:38+00:00" }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, { "name": "felixfbecker/advanced-json-rpc", "version": "v3.2.1", @@ -1056,6 +1250,198 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpbench/container", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.1" + }, + "time": "2022-01-25T10:17:35+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/b013b717832ddbaadf2a40984b04bc66af9a7110", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.2||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.83", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.2" + }, + "time": "2021-09-24T15:26:07+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.2.7", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "dce145304abbb16c8d9af69c19d96f47e9d0e670" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/dce145304abbb16c8d9af69c19d96f47e9d0e670", + "reference": "dce145304abbb16c8d9af69c19d96f47e9d0e670", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.13", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.1", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0", + "symfony/finder": "^4.2 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0", + "symfony/process": "^4.2 || ^5.0 || ^6.0", + "webmozart/glob": "^4.6" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^1.0", + "phpspec/prophecy": "^1.12", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5.8 || ^9.0", + "symfony/error-handler": "^5.2 || ^6.0", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.2.7" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2022-10-15T09:57:51+00:00" + }, { "name": "phpdocumentor/reflection-docblock", "version": "5.3.0", @@ -1757,26 +2143,31 @@ "time": "2022-06-19T12:14:25+00:00" }, { - "name": "psr/container", - "version": "1.1.2", + "name": "psr/cache", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=5.3.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1786,14 +2177,58 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Common interface for caching libraries", "keywords": [ - "PSR-11", - "container", + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", "container-interface", "container-interop", "psr" @@ -2878,6 +3313,70 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "seld/jsonlint", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "4211420d25eba80712bff236a98960ef68b866b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7", + "reference": "4211420d25eba80712bff236a98960ef68b866b7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2022-04-01T13:37:23+00:00" + }, { "name": "symfony/console", "version": "v5.4.11", @@ -3044,6 +3543,202 @@ ], "time": "2022-01-02T09:53:40+00:00" }, + { + "name": "symfony/filesystem", + "version": "v5.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "ac09569844a9109a5966b9438fc29113ce77cf51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51", + "reference": "ac09569844a9109a5966b9438fc29113ce77cf51", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-21T19:53:16+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:37:50+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690", + "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T13:00:38+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.26.0", @@ -3536,6 +4231,68 @@ ], "time": "2022-05-10T07:21:04+00:00" }, + { + "name": "symfony/process", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, { "name": "symfony/service-contracts", "version": "v2.5.2", @@ -3920,6 +4677,55 @@ }, "time": "2022-06-03T18:03:27+00:00" }, + { + "name": "webmozart/glob", + "version": "4.6.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/glob.git", + "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655", + "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/filesystem": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "support": { + "issues": "https://github.com/webmozarts/glob/issues", + "source": "https://github.com/webmozarts/glob/tree/4.6.0" + }, + "time": "2022-05-24T19:45:58+00:00" + }, { "name": "webmozart/path-util", "version": "2.3.0", diff --git a/src/PseudoTypes/ArrayShape.php b/src/PseudoTypes/ArrayShape.php new file mode 100644 index 0000000..48e66f0 --- /dev/null +++ b/src/PseudoTypes/ArrayShape.php @@ -0,0 +1,44 @@ +items = $items; + } + + public function underlyingType(): Type + { + return new Array_(new Mixed_(), new ArrayKey()); + } + + public function __toString(): string + { + return 'array{' . implode(', ', $this->items) . '}'; + } +} diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php new file mode 100644 index 0000000..56ab5dc --- /dev/null +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -0,0 +1,62 @@ +key = $key; + $this->value = $value ?? new Mixed_(); + $this->optional = $optional; + } + + public function getKey(): ?string + { + return $this->key; + } + + public function getValue(): Type + { + return $this->value; + } + + public function isOptional(): bool + { + return $this->optional; + } + + public function __toString(): string + { + if ($this->key !== null) { + return sprintf( + '%s%s: %s', + $this->key, + $this->optional ? '?' : '', + (string) $this->value + ); + } + + return (string) $this->value; + } +} diff --git a/src/PseudoTypes/ConstExpression.php b/src/PseudoTypes/ConstExpression.php new file mode 100644 index 0000000..c2c42bc --- /dev/null +++ b/src/PseudoTypes/ConstExpression.php @@ -0,0 +1,54 @@ +owner = $owner; + $this->expression = $expression; + } + + public function getOwner(): Fqsen + { + return $this->owner; + } + + public function getExpression(): string + { + return $this->expression; + } + + public function underlyingType(): Type + { + return new Mixed_(); + } + + public function __toString(): string + { + return sprintf('%s::%s', (string) $this->owner, $this->expression); + } +} diff --git a/src/PseudoTypes/FloatValue.php b/src/PseudoTypes/FloatValue.php new file mode 100644 index 0000000..778374f --- /dev/null +++ b/src/PseudoTypes/FloatValue.php @@ -0,0 +1,44 @@ +value = $value; + } + + public function getValue(): float + { + return $this->value; + } + + public function underlyingType(): Type + { + return new Float_(); + } + + public function __toString(): string + { + return (string) $this->value; + } +} diff --git a/src/PseudoTypes/IntegerValue.php b/src/PseudoTypes/IntegerValue.php new file mode 100644 index 0000000..e2f3463 --- /dev/null +++ b/src/PseudoTypes/IntegerValue.php @@ -0,0 +1,44 @@ +value = $value; + } + + public function getValue(): int + { + return $this->value; + } + + public function underlyingType(): Type + { + return new Integer(); + } + + public function __toString(): string + { + return (string) $this->value; + } +} diff --git a/src/PseudoTypes/StringValue.php b/src/PseudoTypes/StringValue.php new file mode 100644 index 0000000..c22eff0 --- /dev/null +++ b/src/PseudoTypes/StringValue.php @@ -0,0 +1,46 @@ +value = $value; + } + + public function getValue(): string + { + return $this->value; + } + + public function underlyingType(): Type + { + return new Float_(); + } + + public function __toString(): string + { + return sprintf('"%s"', $this->value); + } +} diff --git a/src/TypeResolver.php b/src/TypeResolver.php index e5695b8..6532655 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -13,12 +13,16 @@ namespace phpDocumentor\Reflection; -use ArrayIterator; use InvalidArgumentException; +use phpDocumentor\Reflection\PseudoTypes\ArrayShape; +use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem; use phpDocumentor\Reflection\PseudoTypes\CallableString; +use phpDocumentor\Reflection\PseudoTypes\ConstExpression; use phpDocumentor\Reflection\PseudoTypes\False_; +use phpDocumentor\Reflection\PseudoTypes\FloatValue; use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; +use phpDocumentor\Reflection\PseudoTypes\IntegerValue; use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\LiteralString; use phpDocumentor\Reflection\PseudoTypes\LowercaseString; @@ -28,12 +32,15 @@ use phpDocumentor\Reflection\PseudoTypes\Numeric_; use phpDocumentor\Reflection\PseudoTypes\NumericString; use phpDocumentor\Reflection\PseudoTypes\PositiveInteger; +use phpDocumentor\Reflection\PseudoTypes\StringValue; use phpDocumentor\Reflection\PseudoTypes\TraitString; use phpDocumentor\Reflection\PseudoTypes\True_; +use phpDocumentor\Reflection\Types\AggregatedType; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\ArrayKey; use phpDocumentor\Reflection\Types\Boolean; use phpDocumentor\Reflection\Types\Callable_; +use phpDocumentor\Reflection\Types\CallableParameter; use phpDocumentor\Reflection\Types\ClassString; use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; @@ -57,46 +64,51 @@ use phpDocumentor\Reflection\Types\String_; use phpDocumentor\Reflection\Types\This; use phpDocumentor\Reflection\Types\Void_; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; +use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; +use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\ParserException; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; use RuntimeException; +use function array_filter; use function array_key_exists; -use function array_key_last; -use function array_pop; -use function array_values; +use function array_map; +use function array_reverse; use function class_exists; use function class_implements; -use function count; -use function current; +use function get_class; use function in_array; -use function is_numeric; -use function preg_split; +use function sprintf; use function strpos; use function strtolower; use function trim; -use const PREG_SPLIT_DELIM_CAPTURE; -use const PREG_SPLIT_NO_EMPTY; - final class TypeResolver { - /** @var string Definition of the ARRAY operator for types */ - private const OPERATOR_ARRAY = '[]'; - /** @var string Definition of the NAMESPACE operator in PHP */ private const OPERATOR_NAMESPACE = '\\'; - /** @var int the iterator parser is inside a compound context */ - private const PARSER_IN_COMPOUND = 0; - - /** @var int the iterator parser is inside a nullable expression context */ - private const PARSER_IN_NULLABLE = 1; - - /** @var int the iterator parser is inside an array expression context */ - private const PARSER_IN_ARRAY_EXPRESSION = 2; - - /** @var int the iterator parser is inside a collection expression context */ - private const PARSER_IN_COLLECTION_EXPRESSION = 3; - /** * @var array List of recognized keywords and unto which Value Object they map * @psalm-var array> @@ -146,6 +158,10 @@ final class TypeResolver /** @psalm-readonly */ private FqsenResolver $fqsenResolver; + /** @psalm-readonly */ + private TypeParser $typeParser; + /** @psalm-readonly */ + private Lexer $lexer; /** * Initializes this TypeResolver with the means to create and resolve Fqsen objects. @@ -153,6 +169,8 @@ final class TypeResolver public function __construct(?FqsenResolver $fqsenResolver = null) { $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver(); + $this->typeParser = new TypeParser(new ConstExprParser()); + $this->lexer = new Lexer(); } /** @@ -165,9 +183,9 @@ public function __construct(?FqsenResolver $fqsenResolver = null) * This method only works as expected if the namespace and aliases are set; * no dynamic reflection is being performed here. * + * @uses Context::getNamespace() to determine with what to prefix the type name. * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be * replaced with another namespace. - * @uses Context::getNamespace() to determine with what to prefix the type name. * * @param string $type The relative or absolute type. */ @@ -182,178 +200,216 @@ public function resolve(string $type, ?Context $context = null): Type $context = new Context(''); } - // split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names - $tokens = preg_split( - '/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/', - $type, - -1, - PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE - ); + try { + $tokens = $this->lexer->tokenize($type); + $tokenIterator = new TokenIterator($tokens); + $ast = $this->typeParser->parse($tokenIterator); + } catch (ParserException $e) { + throw new RuntimeException($e->getMessage(), 0, $e); + } - if ($tokens === false) { - throw new InvalidArgumentException('Unable to split the type string "' . $type . '" into tokens'); + return $this->createType($ast, $context); + } + + public function createType(?TypeNode $type, Context $context): Type + { + if ($type === null) { + return new Mixed_(); } - /** @var ArrayIterator $tokenIterator */ - $tokenIterator = new ArrayIterator($tokens); + switch (get_class($type)) { + case ArrayTypeNode::class: + return new Array_( + $this->createType($type->type, $context) + ); + + case ArrayShapeNode::class: + return new ArrayShape( + ...array_map( + fn (ArrayShapeItemNode $item) => new ArrayShapeItem( + (string) $item->keyName, + $this->createType($item->valueType, $context), + $item->optional + ), + $type->items + ) + ); + + case CallableTypeNode::class: + return $this->createFromCallable($type, $context); + + case ConstTypeNode::class: + return $this->createFromConst($type, $context); + + case GenericTypeNode::class: + return $this->createFromGeneric($type, $context); + + case IdentifierTypeNode::class: + return $this->resolveSingleType($type->name, $context); + + case IntersectionTypeNode::class: + return new Intersection( + array_filter( + array_map( + function (TypeNode $nestedType) use ($context) { + $type = $this->createType($nestedType, $context); + if ($type instanceof AggregatedType) { + return new Expression($type); + } + + return $type; + }, + $type->types + ) + ) + ); + + case NullableTypeNode::class: + $nestedType = $this->createType($type->type, $context); + + return new Nullable($nestedType); + + case UnionTypeNode::class: + return new Compound( + array_filter( + array_map( + function (TypeNode $nestedType) use ($context) { + $type = $this->createType($nestedType, $context); + if ($type instanceof AggregatedType) { + return new Expression($type); + } + + return $type; + }, + $type->types + ) + ) + ); + + case ThisTypeNode::class: + return new This(); - return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND); + case ConditionalTypeNode::class: + case ConditionalTypeForParameterNode::class: + case OffsetAccessTypeNode::class: + default: + return new Mixed_(); + } } - /** - * Analyse each tokens and creates types - * - * @param ArrayIterator $tokens the iterator on tokens - * @param int $parserContext on of self::PARSER_* constants, indicating - * the context where we are in the parsing - */ - private function parseTypes(ArrayIterator $tokens, Context $context, int $parserContext): Type + private function createFromGeneric(GenericTypeNode $type, Context $context): Type { - $types = []; - $token = ''; - $compoundToken = '|'; - while ($tokens->valid()) { - $token = $tokens->current(); - if ($token === null) { - throw new RuntimeException( - 'Unexpected nullable character' - ); - } + switch (strtolower($type->type->name)) { + case 'array': + return $this->createArray($type->genericTypes, $context); - if ($token === '|' || $token === '&') { - if (count($types) === 0) { + case 'class-string': + $subType = $this->createType($type->genericTypes[0], $context); + if (!$subType instanceof Object_ || $subType->getFqsen() === null) { throw new RuntimeException( - 'A type is missing before a type separator' + $subType . ' is not a class string' ); } - if ( - !in_array($parserContext, [ - self::PARSER_IN_COMPOUND, - self::PARSER_IN_ARRAY_EXPRESSION, - self::PARSER_IN_COLLECTION_EXPRESSION, - self::PARSER_IN_NULLABLE, - ], true) - ) { - throw new RuntimeException( - 'Unexpected type separator' - ); - } + return new ClassString( + $subType->getFqsen() + ); - $compoundToken = $token; - $tokens->next(); - } elseif ($token === '?') { - if ( - !in_array($parserContext, [ - self::PARSER_IN_COMPOUND, - self::PARSER_IN_ARRAY_EXPRESSION, - self::PARSER_IN_COLLECTION_EXPRESSION, - self::PARSER_IN_NULLABLE, - ], true) - ) { + case 'interface-string': + $subType = $this->createType($type->genericTypes[0], $context); + if (!$subType instanceof Object_ || $subType->getFqsen() === null) { throw new RuntimeException( - 'Unexpected nullable character' + $subType . ' is not a class string' ); } - $tokens->next(); - $type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE); - $types[] = new Nullable($type); - } elseif ($token === '(') { - $tokens->next(); - $type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION); - - $token = $tokens->current(); - if ($token === null) { // Someone did not properly close their array expression .. - break; - } - - $tokens->next(); + return new InterfaceString( + $subType->getFqsen() + ); - $resolvedType = new Expression($type); + case 'list': + return new List_( + $this->createType($type->genericTypes[0], $context) + ); - $types[] = $resolvedType; - } elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && isset($token[0]) && $token[0] === ')') { - break; - } elseif ($token === '<') { - if (count($types) === 0) { - throw new RuntimeException( - 'Unexpected collection operator "<", class name is missing' - ); + case 'int': + if (isset($type->genericTypes[1]) === false) { + throw new RuntimeException('int has not the correct format'); } - $classType = array_pop($types); - if ($classType !== null) { - if ((string) $classType === 'class-string') { - $types[] = $this->resolveClassString($tokens, $context); - } elseif ((string) $classType === 'int') { - $types[] = $this->resolveIntRange($tokens); - } elseif ((string) $classType === 'interface-string') { - $types[] = $this->resolveInterfaceString($tokens, $context); - } else { - $types[] = $this->resolveCollection($tokens, $classType, $context); - } - } + return new IntegerRange( + (string) $type->genericTypes[0], + (string) $type->genericTypes[1], + ); - $tokens->next(); - } elseif ( - $parserContext === self::PARSER_IN_COLLECTION_EXPRESSION - && ($token === '>' || trim($token) === ',') - ) { - break; - } elseif ($token === self::OPERATOR_ARRAY) { - $last = array_key_last($types); - if ($last === null) { - throw new InvalidArgumentException('Unexpected array operator'); - } + case 'iterable': + return new Iterable_( + ...array_reverse( + array_map( + fn (TypeNode $genericType) => $this->createType($genericType, $context), + $type->genericTypes + ) + ) + ); - $lastItem = $types[$last]; - if ($lastItem instanceof Expression) { - $lastItem = $lastItem->getValueType(); + default: + $collectionType = $this->createType($type->type, $context); + if ($collectionType instanceof Object_ === false) { + throw new RuntimeException(sprintf('%s is not a collection', (string) $collectionType)); } - $types[$last] = new Array_($lastItem); - - $tokens->next(); - } else { - $types[] = $this->resolveSingleType($token, $context); - $tokens->next(); - } + return new Collection( + $collectionType->getFqsen(), + ...array_reverse( + array_map( + fn (TypeNode $genericType) => $this->createType($genericType, $context), + $type->genericTypes + ) + ) + ); } + } - if ($token === '|' || $token === '&') { - throw new RuntimeException( - 'A type is missing after a type separator' - ); - } + private function createFromCallable(CallableTypeNode $type, Context $context): Callable_ + { + return new Callable_( + array_map( + function (CallableTypeParameterNode $param) use ($context) { + return new CallableParameter( + $this->createType($param->type, $context), + $param->parameterName !== '' ? trim($param->parameterName, '$') : null, + $param->isReference, + $param->isVariadic, + $param->isOptional + ); + }, + $type->parameters + ), + $this->createType($type->returnType, $context), + ); + } - if (count($types) === 0) { - if ($parserContext === self::PARSER_IN_NULLABLE) { - throw new RuntimeException( - 'A type is missing after a nullable character' - ); - } + private function createFromConst(ConstTypeNode $type, Context $context): Type + { + switch (true) { + case $type->constExpr instanceof ConstExprIntegerNode: + return new IntegerValue((int) $type->constExpr->value); - if ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION) { - throw new RuntimeException( - 'A type is missing in an array expression' - ); - } + case $type->constExpr instanceof ConstExprFloatNode: + return new FloatValue((float) $type->constExpr->value); - if ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION) { - throw new RuntimeException( - 'A type is missing in a collection expression' + case $type->constExpr instanceof ConstExprStringNode: + return new StringValue($type->constExpr->value); + + case $type->constExpr instanceof ConstFetchNode: + return new ConstExpression( + $this->fqsenResolver->resolve($type->constExpr->className, $context), + $type->constExpr->name ); - } - } elseif (count($types) === 1) { - return current($types); - } - if ($compoundToken === '|') { - return new Compound(array_values($types)); + default: + throw new RuntimeException(sprintf('Unsupported constant type %s', get_class($type))); } - - return new Intersection(array_values($types)); } /** @@ -475,244 +531,24 @@ private function resolveTypedObject(string $type, ?Context $context = null): Obj return new Object_($this->fqsenResolver->resolve($type, $context)); } - /** - * Resolves class string - * - * @param ArrayIterator $tokens - */ - private function resolveClassString(ArrayIterator $tokens, Context $context): Type + /** @param TypeNode[] $typeNodes */ + private function createArray(array $typeNodes, Context $context): Array_ { - $tokens->next(); - - $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - - if (!$classType instanceof Object_ || $classType->getFqsen() === null) { - throw new RuntimeException( - $classType . ' is not a class string' - ); - } - - $token = $tokens->current(); - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException( - 'class-string: ">" is missing' - ); - } - - throw new RuntimeException( - 'Unexpected character "' . $token . '", ">" is missing' - ); - } - - return new ClassString($classType->getFqsen()); - } - - /** - * Resolves integer ranges - * - * @param ArrayIterator $tokens - */ - private function resolveIntRange(ArrayIterator $tokens): Type - { - $tokens->next(); - - $token = ''; - $minValue = null; - $maxValue = null; - $commaFound = false; - $tokenCounter = 0; - while ($tokens->valid()) { - $tokenCounter++; - $token = $tokens->current(); - if ($token === null) { - throw new RuntimeException( - 'Unexpected nullable character' - ); - } - - $token = trim($token); - - if ($token === '>') { - break; - } - - if ($token === ',') { - $commaFound = true; - } - - if ($commaFound === false && $minValue === null) { - if (is_numeric($token) || $token === 'max' || $token === 'min') { - $minValue = $token; - } - } - - if ($commaFound === true && $maxValue === null) { - if (is_numeric($token) || $token === 'max' || $token === 'min') { - $maxValue = $token; - } - } - - $tokens->next(); - } - - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException( - 'interface-string: ">" is missing' - ); - } - - throw new RuntimeException( - 'Unexpected character "' . $token . '", ">" is missing' - ); - } - - if ($minValue === null || $maxValue === null || $tokenCounter > 4) { - throw new RuntimeException( - 'int has not the correct format' - ); - } - - return new IntegerRange($minValue, $maxValue); - } - - /** - * Resolves class string - * - * @param ArrayIterator $tokens - */ - private function resolveInterfaceString(ArrayIterator $tokens, Context $context): Type - { - $tokens->next(); - - $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - - if (!$classType instanceof Object_ || $classType->getFqsen() === null) { - throw new RuntimeException( - $classType . ' is not a interface string' - ); - } - - $token = $tokens->current(); - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException( - 'interface-string: ">" is missing' - ); - } - - throw new RuntimeException( - 'Unexpected character "' . $token . '", ">" is missing' - ); - } - - return new InterfaceString($classType->getFqsen()); - } - - /** - * Resolves the collection values and keys - * - * @param ArrayIterator $tokens - * - * @return Array_|Iterable_|Collection - */ - private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context): Type - { - $isArray = ((string) $classType === 'array'); - $isIterable = ((string) $classType === 'iterable'); - $isList = ((string) $classType === 'list'); - - // allow only "array", "iterable" or class name before "<" - if ( - !$isArray && !$isIterable && !$isList - && (!$classType instanceof Object_ || $classType->getFqsen() === null) - ) { - throw new RuntimeException( - $classType . ' is not a collection' - ); - } - - $tokens->next(); - - $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - $keyType = null; - - $token = $tokens->current(); - if ($token !== null && trim($token) === ',' && !$isList) { - // if we have a comma, then we just parsed the key type, not the value type - $keyType = $valueType; - if ($isArray) { - // check the key type for an "array" collection. We allow only - // strings or integers. - if ( - !$keyType instanceof ArrayKey && - !$keyType instanceof String_ && - !$keyType instanceof Integer && - !$keyType instanceof Compound - ) { - throw new RuntimeException( - 'An array can have only integers or strings as keys' - ); - } - - if ($keyType instanceof Compound) { - foreach ($keyType->getIterator() as $item) { - if ( - !$item instanceof ArrayKey && - !$item instanceof String_ && - !$item instanceof Integer - ) { - throw new RuntimeException( - 'An array can have only integers or strings as keys' - ); - } - } - } - } - - $tokens->next(); - // now let's parse the value type - $valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); - } - - $token = $tokens->current(); - if ($token !== '>') { - if (empty($token)) { - throw new RuntimeException( - 'Collection: ">" is missing' - ); - } - - throw new RuntimeException( - 'Unexpected character "' . $token . '", ">" is missing' - ); - } - - if ($isArray) { - return new Array_($valueType, $keyType); - } - - if ($isIterable) { - return new Iterable_($valueType, $keyType); - } + $types = array_reverse( + array_map( + fn (TypeNode $node) => $this->createType($node, $context), + $typeNodes + ) + ); - if ($isList) { - return new List_($valueType); + if (isset($types[1]) === false) { + return new Array_(...$types); } - if ($classType instanceof Object_) { - return $this->makeCollectionFromObject($classType, $valueType, $keyType); + if ($types[1] instanceof String_ || $types[1] instanceof Integer || $types[1] instanceof ArrayKey) { + return new Array_(...$types); } - throw new RuntimeException('Invalid $classType provided'); - } - - /** - * @psalm-pure - */ - private function makeCollectionFromObject(Object_ $object, Type $valueType, ?Type $keyType = null): Collection - { - return new Collection($object->getFqsen(), $valueType, $keyType); + throw new RuntimeException('An array can have only integers or strings as keys'); } } diff --git a/src/Types/AggregatedType.php b/src/Types/AggregatedType.php index 257ed51..6f9e879 100644 --- a/src/Types/AggregatedType.php +++ b/src/Types/AggregatedType.php @@ -106,7 +106,7 @@ public function getIterator(): ArrayIterator */ private function add(Type $type): void { - if ($type instanceof self) { + if ($type instanceof static) { foreach ($type->getIterator() as $subType) { $this->add($subType); } diff --git a/src/Types/CallableParameter.php b/src/Types/CallableParameter.php new file mode 100644 index 0000000..1a36d23 --- /dev/null +++ b/src/Types/CallableParameter.php @@ -0,0 +1,72 @@ +type = $type; + $this->isReference = $isReference; + $this->isVariadic = $isVariadic; + $this->isOptional = $isOptional; + $this->name = $name; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } + + public function isReference(): bool + { + return $this->isReference; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + public function isOptional(): bool + { + return $this->isOptional; + } +} diff --git a/src/Types/Callable_.php b/src/Types/Callable_.php index 4e67aa4..20c5726 100644 --- a/src/Types/Callable_.php +++ b/src/Types/Callable_.php @@ -22,6 +22,30 @@ */ final class Callable_ implements Type { + private ?Type $returnType; + /** @var CallableParameter[] */ + private array $parameters; + + /** + * @param CallableParameter[] $parameters + */ + public function __construct(array $parameters = [], ?Type $returnType = null) + { + $this->parameters = $parameters; + $this->returnType = $returnType; + } + + /** @return CallableParameter[] */ + public function getParameters(): array + { + return $this->parameters; + } + + public function getReturnType(): ?Type + { + return $this->returnType; + } + /** * Returns a rendered output of the Type as it would be used in a DocBlock. */ diff --git a/tests/unit/CollectionResolverTest.php b/tests/unit/CollectionResolverTest.php index eb25b04..3468d94 100644 --- a/tests/unit/CollectionResolverTest.php +++ b/tests/unit/CollectionResolverTest.php @@ -13,7 +13,6 @@ namespace phpDocumentor\Reflection; -use InvalidArgumentException; use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Collection; @@ -169,23 +168,6 @@ public function testResolvingArrayCollectionWithKeyAndWhitespace(): void $this->assertInstanceOf(Compound::class, $valueType); } - /** - * @uses \phpDocumentor\Reflection\Types\Context - * @uses \phpDocumentor\Reflection\Types\Compound - * @uses \phpDocumentor\Reflection\Types\Collection - * @uses \phpDocumentor\Reflection\Types\String_ - * - * @covers ::__construct - * @covers ::resolve - */ - public function testResolvingArrayCollectionWithKeyAndTooManyWhitespace(): void - { - $this->expectException(InvalidArgumentException::class); - $fixture = new TypeResolver(); - - $fixture->resolve('array', new Context('')); - } - /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Compound @@ -260,7 +242,7 @@ public function testGoodArrayCollectionKey(): void public function testMissingStartCollection(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unexpected collection operator "<", class name is missing'); + $this->expectExceptionMessage('Unexpected token "<", expected type at offset 0'); $fixture = new TypeResolver(); $fixture->resolve('', new Context('')); } @@ -272,7 +254,7 @@ public function testMissingStartCollection(): void public function testMissingEndCollection(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Collection: ">" is missing'); + $this->expectExceptionMessage('Unexpected token "", expected \'>\' at offset 25'); $fixture = new TypeResolver(); $fixture->resolve('ArrayObjectexpectException(RuntimeException::class); $this->expectExceptionMessage('int has not the correct format'); @@ -104,7 +104,7 @@ public function testResolvingIntRangeErrorMisingMaxValue(): void public function testResolvingIntRangeErrorMisingMinValue(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('int has not the correct format'); + $this->expectExceptionMessage('Unexpected token ",", expected type at offset 4'); $fixture = new TypeResolver(); $resolvedType = $fixture->resolve('int<,max>', new Context('')); @@ -140,27 +140,9 @@ public function testResolvingIntRangeErrorMisingComma(): void public function testResolvingIntRangeErrorMissingEnd(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unexpected character "max", ">" is missing'); + $this->expectExceptionMessage('Unexpected token "", expected \'>\' at offset 11'); $fixture = new TypeResolver(); $resolvedType = $fixture->resolve('intexpectException(RuntimeException::class); - $this->expectExceptionMessage('int has not the correct format'); - - $fixture = new TypeResolver(); - $resolvedType = $fixture->resolve('int', new Context('')); - } } diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index be168f0..52e3666 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -15,8 +15,12 @@ use InvalidArgumentException; use phpDocumentor\Reflection\PseudoTypes\CallableString; +use phpDocumentor\Reflection\PseudoTypes\ConstExpression; use phpDocumentor\Reflection\PseudoTypes\False_; +use phpDocumentor\Reflection\PseudoTypes\FloatValue; use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString; +use phpDocumentor\Reflection\PseudoTypes\IntegerRange; +use phpDocumentor\Reflection\PseudoTypes\IntegerValue; use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\PseudoTypes\LiteralString; use phpDocumentor\Reflection\PseudoTypes\LowercaseString; @@ -26,13 +30,16 @@ use phpDocumentor\Reflection\PseudoTypes\Numeric_; use phpDocumentor\Reflection\PseudoTypes\NumericString; use phpDocumentor\Reflection\PseudoTypes\PositiveInteger; +use phpDocumentor\Reflection\PseudoTypes\StringValue; use phpDocumentor\Reflection\PseudoTypes\TraitString; use phpDocumentor\Reflection\PseudoTypes\True_; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\ArrayKey; use phpDocumentor\Reflection\Types\Boolean; use phpDocumentor\Reflection\Types\Callable_; +use phpDocumentor\Reflection\Types\CallableParameter; use phpDocumentor\Reflection\Types\ClassString; +use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; @@ -433,6 +440,7 @@ public function testResolvingCompoundTypedArrayTypes(): void */ public function testResolvingNullableCompoundTypes(): void { + $this->markTestSkipped('Invalid type definition'); $fixture = new TypeResolver(); // Note that in PHP types it is illegal to use shorthand nullable @@ -560,12 +568,9 @@ public function testResolvingArrayOfArrayExpressionTypes(): void */ public function testReturnEmptyCompoundOnAnUnclosedArrayExpressionType(): void { + $this->expectException(RuntimeException::class); $fixture = new TypeResolver(); - - $resolvedType = $fixture->resolve('(string|\stdClass', new Context('')); - - $this->assertInstanceOf(Compound::class, $resolvedType); - $this->assertSame('', (string) $resolvedType); + $fixture->resolve('(string|\stdClass', new Context('')); } /** @@ -752,7 +757,7 @@ public function testExceptionIsThrownIfTypeIsEmpty(): void */ public function testInvalidArrayOperator(): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(RuntimeException::class); $fixture = new TypeResolver(); $fixture->resolve('[]', new Context('')); } @@ -863,4 +868,237 @@ public function testArrayKeyValueSpecification(): void $this->assertEquals(new Array_(new Array_(new String_(), new Integer()), new String_()), $type); } + + /** + * @covers ::__construct + * @covers ::resolve + * @dataProvider typeProvider + * @dataProvider genericsProvider + * @dataProvider callableProvider + * @dataProvider constExpressions + * @testdox create type from $type + */ + public function testTypeBuilding(string $type, Type $expected): void + { + $fixture = new TypeResolver(); + $actual = $fixture->resolve($type, new Context('phpDocumentor')); + + self::assertEquals($expected, $actual); + } + + /** + * @return array + */ + public function typeProvider(): array + { + return [ + [ + 'string', + new String_(), + ], + [ + '( string )', + new String_(), + ], + [ + '\\Foo\Bar\\Baz', + new Object_(new Fqsen('\\Foo\Bar\\Baz')), + ], + [ + 'string|int', + new Compound( + [ + new String_(), + new Integer(), + ] + ), + ], + [ + 'string&int', + new Intersection( + [ + new String_(), + new Integer(), + ] + ), + ], + [ + 'string & (int | float)', + new Intersection( + [ + new String_(), + new Expression( + new Compound( + [ + new Integer(), + new Float_(), + ] + ) + ), + ] + ), + ], + [ + 'string[]', + new Array_( + new String_() + ), + ], + [ + '$this', + new This(), + ], + [ + '?int', + new Nullable( + new Integer() + ), + ], + [ + 'self', + new Self_(), + ], + ]; + } + + /** + * @return array + */ + public function genericsProvider(): array + { + return [ + [ + 'array', + new Array_( + new Object_(new Fqsen('\\phpDocumentor\\Foo\\Bar')), + new Integer() + ), + ], + [ + 'Collection[]', + new Array_( + new Collection( + new Fqsen('\\phpDocumentor\\Collection'), + new Integer(), + new ArrayKey() + ) + ), + ], + [ + 'class-string', + new ClassString(null), + ], + [ + 'class-string', + new ClassString(new Fqsen('\\phpDocumentor\\Foo')), + ], + [ + 'interface-string', + new InterfaceString(new Fqsen('\\phpDocumentor\\Foo')), + ], + [ + 'List', + new List_(new Object_(new Fqsen('\\phpDocumentor\\Foo'))), + ], + [ + 'int<1, 100>', + new IntegerRange('1', '100'), + ], + ]; + } + + /** + * @return array + */ + public function callableProvider(): array + { + return [ + [ + 'callable', + new Callable_(), + ], + [ + 'callable()', + new Callable_(), + ], + [ + 'callable(): Foo', + new Callable_([], new Object_(new Fqsen('\\phpDocumentor\\Foo'))), + ], + [ + 'callable(): (Foo&Bar)', + new Callable_( + [], + new Intersection( + [ + new Object_(new Fqsen('\\phpDocumentor\\Foo')), + new Object_(new Fqsen('\\phpDocumentor\\Bar')), + ] + ) + ), + ], + [ + 'callable(A&...$a=, B&...=, C): Foo', + new Callable_( + [ + new CallableParameter( + new Object_(new Fqsen('\\phpDocumentor\\A')), + 'a', + true, + true, + true + ), + new CallableParameter( + new Object_(new Fqsen('\\phpDocumentor\\B')), + null, + true, + true, + true + ), + new CallableParameter( + new Object_(new Fqsen('\\phpDocumentor\\C')), + null, + false, + false, + false + ), + ], + new Object_(new Fqsen('\\phpDocumentor\\Foo')) + ), + ], + ]; + } + + /** + * @return array + */ + public function constExpressions(): array + { + return [ + [ + '123', + new IntegerValue(123), + ], + [ + 'true', + new True_(), + ], + [ + '123.2', + new FloatValue(123.2), + ], + [ + '"bar"', + new StringValue('bar'), + ], + [ + 'Foo::FOO_CONSTANT', + new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_CONSTANT'), + ], + [ + 'Foo::FOO_*', + new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_*'), + ], + ]; + } } diff --git a/tests/unit/Types/CompoundTest.php b/tests/unit/Types/CompoundTest.php index d5e08af..494d423 100644 --- a/tests/unit/Types/CompoundTest.php +++ b/tests/unit/Types/CompoundTest.php @@ -143,4 +143,34 @@ public function testCompoundCanBeIterated(): void $this->assertSame($types[$index], $type); } } + + /** + * @uses \phpDocumentor\Reflection\Types\Integer + * @uses \phpDocumentor\Reflection\Types\Boolean + * + * @covers ::__construct + * @covers ::__toString + */ + public function testCompoundIsMergedWithCompound(): void + { + $compound = new Compound([new Integer(), new Compound([new Integer(), new Boolean()])]); + + $this->assertCount(2, iterator_to_array($compound)); + $this->assertSame('int|bool', (string) $compound); + } + + /** + * @uses \phpDocumentor\Reflection\Types\Integer + * @uses \phpDocumentor\Reflection\Types\Boolean + * + * @covers ::__construct + * @covers ::__toString + */ + public function testCompoundIsMergedOnMergedWithIntersection(): void + { + $compound = new Compound([new Integer(), new Intersection([new Integer(), new Boolean()])]); + + $this->assertCount(2, iterator_to_array($compound)); + $this->assertSame('int|int&bool', (string) $compound); + } } diff --git a/tests/unit/Types/ContextFactoryTest.php b/tests/unit/Types/ContextFactoryTest.php index 376fc8c..28a3d5d 100644 --- a/tests/unit/Types/ContextFactoryTest.php +++ b/tests/unit/Types/ContextFactoryTest.php @@ -180,7 +180,7 @@ public function assertNamespaceAliasesFrom(Context $context) 'Assert' => Assert::class, 'e' => e::class, ReflectionClass::class => ReflectionClass::class, - 'stdClass' => 'stdClass', + \stdClass::class => \stdClass::class, ]; $actual = $context->getNamespaceAliases();