├── .gitignore ├── .php_cs ├── LICENSE ├── README.md ├── build.sh ├── composer.json ├── composer.lock ├── lib └── PHPTypes │ ├── InternalArgInfo.php │ ├── State.php │ ├── Type.php │ ├── TypeInferer.php │ ├── TypeReconstructor.php │ └── TypeResolver.php ├── phpunit.xml.dist └── test ├── PHPTypes ├── ReconstructorTest.php └── TypeTest.php └── code ├── basic.test ├── bitwiseNot.test └── class.test /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | level(Symfony\CS\FixerInterface::NONE_LEVEL) 14 | ->fixers([ 15 | 'header_comment', 16 | 'linefeed', 17 | 'indentation', 18 | 'elseif', 19 | 'line_after_namespace', 20 | 'lowercase_constants', 21 | 'lowercase_keywords', 22 | 'method_argument_space', 23 | 'single_blank_line_before_namespace', 24 | 'ordered_use', 25 | 'short_array_syntax', 26 | 'single_line_after_imports', 27 | 'visibility', 28 | 'trailing_spaces', 29 | 'concat_with_spaces', 30 | 'align_double_arrow', 31 | 'unused_use', 32 | 'ternary_spaces', 33 | 'remove_leading_slash_use', 34 | 'remove_lines_between_uses', 35 | 'phpdoc_indent', 36 | 'phpdoc_no_access', 37 | 'phpdoc_params', 38 | 'phpdoc_scalar', 39 | 'phpdoc_separation', 40 | 'phpdoc_trim', 41 | 'phpdoc_var_without_name', 42 | 'phpdoc_order', 43 | 'no_empty_lines_after_phpdocs', 44 | ]) 45 | ->finder( 46 | Symfony\CS\Finder\DefaultFinder::create() 47 | ->in(__DIR__ . "/lib") 48 | ->in(__DIR__ . "/test") 49 | ) 50 | ; 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Anthony Ferrara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Types 2 | 3 | A PHP Type reconstruction library!!! 4 | 5 | This uses [PHP-CFG](https://github.com/ircmaxell/php-cfg) as a base to reconstruct and infer types onto the graph. 6 | 7 | For usage, check the tests... 8 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | vendor/bin/php-cs-fixer fix 4 | vendor/bin/phpunit -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ircmaxell/php-types", 3 | "description": "A PHP CFG Type Inference / Reconstruction Engine", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Anthony Ferrara", 8 | "email": "ircmaxell@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.5", 13 | "ircmaxell/php-cfg": "^0.5" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^4.7", 17 | "fabpot/php-cs-fixer": "1.*" 18 | }, 19 | "minimum-stability": "dev", 20 | "prefer-stable": true, 21 | "autoload": { 22 | "psr-4": { 23 | "PHPTypes\\": "lib/PHPTypes/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "920f3deb4bf06a07cad2a83618a8b996", 8 | "packages": [ 9 | { 10 | "name": "ircmaxell/php-cfg", 11 | "version": "v0.5.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/ircmaxell/php-cfg.git", 15 | "reference": "4476ba9d832be2af0322166e531b8d78562af8d7" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/ircmaxell/php-cfg/zipball/4476ba9d832be2af0322166e531b8d78562af8d7", 20 | "reference": "4476ba9d832be2af0322166e531b8d78562af8d7", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "nikic/php-parser": "^4.0", 25 | "php": "^7.0", 26 | "phpdocumentor/graphviz": "^1.0.4" 27 | }, 28 | "require-dev": { 29 | "fabpot/php-cs-fixer": "1.*", 30 | "phpunit/phpunit": "^4.7" 31 | }, 32 | "type": "library", 33 | "autoload": { 34 | "psr-4": { 35 | "PHPCfg\\": "lib/PHPCfg/" 36 | } 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "BSD" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Anthony Ferrara", 45 | "email": "ircmaxell@gmail.com" 46 | } 47 | ], 48 | "description": "A Control Flow Graph implementation for PHP", 49 | "time": "2019-02-23T22:06:54+00:00" 50 | }, 51 | { 52 | "name": "nikic/php-parser", 53 | "version": "v4.2.1", 54 | "source": { 55 | "type": "git", 56 | "url": "https://github.com/nikic/PHP-Parser.git", 57 | "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0" 58 | }, 59 | "dist": { 60 | "type": "zip", 61 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/5221f49a608808c1e4d436df32884cbc1b821ac0", 62 | "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0", 63 | "shasum": "" 64 | }, 65 | "require": { 66 | "ext-tokenizer": "*", 67 | "php": ">=7.0" 68 | }, 69 | "require-dev": { 70 | "phpunit/phpunit": "^6.5 || ^7.0" 71 | }, 72 | "bin": [ 73 | "bin/php-parse" 74 | ], 75 | "type": "library", 76 | "extra": { 77 | "branch-alias": { 78 | "dev-master": "4.2-dev" 79 | } 80 | }, 81 | "autoload": { 82 | "psr-4": { 83 | "PhpParser\\": "lib/PhpParser" 84 | } 85 | }, 86 | "notification-url": "https://packagist.org/downloads/", 87 | "license": [ 88 | "BSD-3-Clause" 89 | ], 90 | "authors": [ 91 | { 92 | "name": "Nikita Popov" 93 | } 94 | ], 95 | "description": "A PHP parser written in PHP", 96 | "keywords": [ 97 | "parser", 98 | "php" 99 | ], 100 | "time": "2019-02-16T20:54:15+00:00" 101 | }, 102 | { 103 | "name": "phpdocumentor/graphviz", 104 | "version": "1.0.4", 105 | "source": { 106 | "type": "git", 107 | "url": "https://github.com/phpDocumentor/GraphViz.git", 108 | "reference": "a906a90a9f230535f25ea31caf81b2323956283f" 109 | }, 110 | "dist": { 111 | "type": "zip", 112 | "url": "https://api.github.com/repos/phpDocumentor/GraphViz/zipball/a906a90a9f230535f25ea31caf81b2323956283f", 113 | "reference": "a906a90a9f230535f25ea31caf81b2323956283f", 114 | "shasum": "" 115 | }, 116 | "require": { 117 | "php": ">=5.3.3" 118 | }, 119 | "require-dev": { 120 | "phpunit/phpunit": "~4.0" 121 | }, 122 | "type": "library", 123 | "autoload": { 124 | "psr-0": { 125 | "phpDocumentor": [ 126 | "src/", 127 | "tests/unit" 128 | ] 129 | } 130 | }, 131 | "notification-url": "https://packagist.org/downloads/", 132 | "license": [ 133 | "MIT" 134 | ], 135 | "authors": [ 136 | { 137 | "name": "Mike van Riel", 138 | "email": "mike.vanriel@naenius.com" 139 | } 140 | ], 141 | "time": "2016-02-02T13:00:08+00:00" 142 | } 143 | ], 144 | "packages-dev": [ 145 | { 146 | "name": "doctrine/instantiator", 147 | "version": "1.0.5", 148 | "source": { 149 | "type": "git", 150 | "url": "https://github.com/doctrine/instantiator.git", 151 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 152 | }, 153 | "dist": { 154 | "type": "zip", 155 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 156 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 157 | "shasum": "" 158 | }, 159 | "require": { 160 | "php": ">=5.3,<8.0-DEV" 161 | }, 162 | "require-dev": { 163 | "athletic/athletic": "~0.1.8", 164 | "ext-pdo": "*", 165 | "ext-phar": "*", 166 | "phpunit/phpunit": "~4.0", 167 | "squizlabs/php_codesniffer": "~2.0" 168 | }, 169 | "type": "library", 170 | "extra": { 171 | "branch-alias": { 172 | "dev-master": "1.0.x-dev" 173 | } 174 | }, 175 | "autoload": { 176 | "psr-4": { 177 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 178 | } 179 | }, 180 | "notification-url": "https://packagist.org/downloads/", 181 | "license": [ 182 | "MIT" 183 | ], 184 | "authors": [ 185 | { 186 | "name": "Marco Pivetta", 187 | "email": "ocramius@gmail.com", 188 | "homepage": "http://ocramius.github.com/" 189 | } 190 | ], 191 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 192 | "homepage": "https://github.com/doctrine/instantiator", 193 | "keywords": [ 194 | "constructor", 195 | "instantiate" 196 | ], 197 | "time": "2015-06-14T21:17:01+00:00" 198 | }, 199 | { 200 | "name": "fabpot/php-cs-fixer", 201 | "version": "v1.13.3", 202 | "source": { 203 | "type": "git", 204 | "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", 205 | "reference": "387e4c86c9dc0e1e4c475291fc114ec45b98e624" 206 | }, 207 | "dist": { 208 | "type": "zip", 209 | "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/387e4c86c9dc0e1e4c475291fc114ec45b98e624", 210 | "reference": "387e4c86c9dc0e1e4c475291fc114ec45b98e624", 211 | "shasum": "" 212 | }, 213 | "require": { 214 | "ext-tokenizer": "*", 215 | "php": "^5.3.6 || >=7.0 <7.2", 216 | "sebastian/diff": "^1.1", 217 | "symfony/console": "^2.3 || ^3.0", 218 | "symfony/event-dispatcher": "^2.1 || ^3.0", 219 | "symfony/filesystem": "^2.1 || ^3.0", 220 | "symfony/finder": "^2.1 || ^3.0", 221 | "symfony/process": "^2.3 || ^3.0", 222 | "symfony/stopwatch": "^2.5 || ^3.0" 223 | }, 224 | "conflict": { 225 | "hhvm": "<3.9" 226 | }, 227 | "require-dev": { 228 | "phpunit/phpunit": "^4.5|^5", 229 | "satooshi/php-coveralls": "^1.0" 230 | }, 231 | "bin": [ 232 | "php-cs-fixer" 233 | ], 234 | "type": "application", 235 | "autoload": { 236 | "psr-4": { 237 | "Symfony\\CS\\": "Symfony/CS/" 238 | } 239 | }, 240 | "notification-url": "https://packagist.org/downloads/", 241 | "license": [ 242 | "MIT" 243 | ], 244 | "authors": [ 245 | { 246 | "name": "Dariusz Rumiński", 247 | "email": "dariusz.ruminski@gmail.com" 248 | }, 249 | { 250 | "name": "Fabien Potencier", 251 | "email": "fabien@symfony.com" 252 | } 253 | ], 254 | "description": "A tool to automatically fix PHP code style", 255 | "abandoned": "friendsofphp/php-cs-fixer", 256 | "time": "2017-09-11T14:11:16+00:00" 257 | }, 258 | { 259 | "name": "phpdocumentor/reflection-common", 260 | "version": "1.0.1", 261 | "source": { 262 | "type": "git", 263 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 264 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" 265 | }, 266 | "dist": { 267 | "type": "zip", 268 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 269 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 270 | "shasum": "" 271 | }, 272 | "require": { 273 | "php": ">=5.5" 274 | }, 275 | "require-dev": { 276 | "phpunit/phpunit": "^4.6" 277 | }, 278 | "type": "library", 279 | "extra": { 280 | "branch-alias": { 281 | "dev-master": "1.0.x-dev" 282 | } 283 | }, 284 | "autoload": { 285 | "psr-4": { 286 | "phpDocumentor\\Reflection\\": [ 287 | "src" 288 | ] 289 | } 290 | }, 291 | "notification-url": "https://packagist.org/downloads/", 292 | "license": [ 293 | "MIT" 294 | ], 295 | "authors": [ 296 | { 297 | "name": "Jaap van Otterdijk", 298 | "email": "opensource@ijaap.nl" 299 | } 300 | ], 301 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 302 | "homepage": "http://www.phpdoc.org", 303 | "keywords": [ 304 | "FQSEN", 305 | "phpDocumentor", 306 | "phpdoc", 307 | "reflection", 308 | "static analysis" 309 | ], 310 | "time": "2017-09-11T18:02:19+00:00" 311 | }, 312 | { 313 | "name": "phpdocumentor/reflection-docblock", 314 | "version": "4.3.0", 315 | "source": { 316 | "type": "git", 317 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 318 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08" 319 | }, 320 | "dist": { 321 | "type": "zip", 322 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", 323 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08", 324 | "shasum": "" 325 | }, 326 | "require": { 327 | "php": "^7.0", 328 | "phpdocumentor/reflection-common": "^1.0.0", 329 | "phpdocumentor/type-resolver": "^0.4.0", 330 | "webmozart/assert": "^1.0" 331 | }, 332 | "require-dev": { 333 | "doctrine/instantiator": "~1.0.5", 334 | "mockery/mockery": "^1.0", 335 | "phpunit/phpunit": "^6.4" 336 | }, 337 | "type": "library", 338 | "extra": { 339 | "branch-alias": { 340 | "dev-master": "4.x-dev" 341 | } 342 | }, 343 | "autoload": { 344 | "psr-4": { 345 | "phpDocumentor\\Reflection\\": [ 346 | "src/" 347 | ] 348 | } 349 | }, 350 | "notification-url": "https://packagist.org/downloads/", 351 | "license": [ 352 | "MIT" 353 | ], 354 | "authors": [ 355 | { 356 | "name": "Mike van Riel", 357 | "email": "me@mikevanriel.com" 358 | } 359 | ], 360 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 361 | "time": "2017-11-30T07:14:17+00:00" 362 | }, 363 | { 364 | "name": "phpdocumentor/type-resolver", 365 | "version": "0.4.0", 366 | "source": { 367 | "type": "git", 368 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 369 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 370 | }, 371 | "dist": { 372 | "type": "zip", 373 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 374 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 375 | "shasum": "" 376 | }, 377 | "require": { 378 | "php": "^5.5 || ^7.0", 379 | "phpdocumentor/reflection-common": "^1.0" 380 | }, 381 | "require-dev": { 382 | "mockery/mockery": "^0.9.4", 383 | "phpunit/phpunit": "^5.2||^4.8.24" 384 | }, 385 | "type": "library", 386 | "extra": { 387 | "branch-alias": { 388 | "dev-master": "1.0.x-dev" 389 | } 390 | }, 391 | "autoload": { 392 | "psr-4": { 393 | "phpDocumentor\\Reflection\\": [ 394 | "src/" 395 | ] 396 | } 397 | }, 398 | "notification-url": "https://packagist.org/downloads/", 399 | "license": [ 400 | "MIT" 401 | ], 402 | "authors": [ 403 | { 404 | "name": "Mike van Riel", 405 | "email": "me@mikevanriel.com" 406 | } 407 | ], 408 | "time": "2017-07-14T14:27:02+00:00" 409 | }, 410 | { 411 | "name": "phpspec/prophecy", 412 | "version": "1.8.0", 413 | "source": { 414 | "type": "git", 415 | "url": "https://github.com/phpspec/prophecy.git", 416 | "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" 417 | }, 418 | "dist": { 419 | "type": "zip", 420 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", 421 | "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", 422 | "shasum": "" 423 | }, 424 | "require": { 425 | "doctrine/instantiator": "^1.0.2", 426 | "php": "^5.3|^7.0", 427 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 428 | "sebastian/comparator": "^1.1|^2.0|^3.0", 429 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 430 | }, 431 | "require-dev": { 432 | "phpspec/phpspec": "^2.5|^3.2", 433 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 434 | }, 435 | "type": "library", 436 | "extra": { 437 | "branch-alias": { 438 | "dev-master": "1.8.x-dev" 439 | } 440 | }, 441 | "autoload": { 442 | "psr-0": { 443 | "Prophecy\\": "src/" 444 | } 445 | }, 446 | "notification-url": "https://packagist.org/downloads/", 447 | "license": [ 448 | "MIT" 449 | ], 450 | "authors": [ 451 | { 452 | "name": "Konstantin Kudryashov", 453 | "email": "ever.zet@gmail.com", 454 | "homepage": "http://everzet.com" 455 | }, 456 | { 457 | "name": "Marcello Duarte", 458 | "email": "marcello.duarte@gmail.com" 459 | } 460 | ], 461 | "description": "Highly opinionated mocking framework for PHP 5.3+", 462 | "homepage": "https://github.com/phpspec/prophecy", 463 | "keywords": [ 464 | "Double", 465 | "Dummy", 466 | "fake", 467 | "mock", 468 | "spy", 469 | "stub" 470 | ], 471 | "time": "2018-08-05T17:53:17+00:00" 472 | }, 473 | { 474 | "name": "phpunit/php-code-coverage", 475 | "version": "2.2.4", 476 | "source": { 477 | "type": "git", 478 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 479 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" 480 | }, 481 | "dist": { 482 | "type": "zip", 483 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", 484 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", 485 | "shasum": "" 486 | }, 487 | "require": { 488 | "php": ">=5.3.3", 489 | "phpunit/php-file-iterator": "~1.3", 490 | "phpunit/php-text-template": "~1.2", 491 | "phpunit/php-token-stream": "~1.3", 492 | "sebastian/environment": "^1.3.2", 493 | "sebastian/version": "~1.0" 494 | }, 495 | "require-dev": { 496 | "ext-xdebug": ">=2.1.4", 497 | "phpunit/phpunit": "~4" 498 | }, 499 | "suggest": { 500 | "ext-dom": "*", 501 | "ext-xdebug": ">=2.2.1", 502 | "ext-xmlwriter": "*" 503 | }, 504 | "type": "library", 505 | "extra": { 506 | "branch-alias": { 507 | "dev-master": "2.2.x-dev" 508 | } 509 | }, 510 | "autoload": { 511 | "classmap": [ 512 | "src/" 513 | ] 514 | }, 515 | "notification-url": "https://packagist.org/downloads/", 516 | "license": [ 517 | "BSD-3-Clause" 518 | ], 519 | "authors": [ 520 | { 521 | "name": "Sebastian Bergmann", 522 | "email": "sb@sebastian-bergmann.de", 523 | "role": "lead" 524 | } 525 | ], 526 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 527 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 528 | "keywords": [ 529 | "coverage", 530 | "testing", 531 | "xunit" 532 | ], 533 | "time": "2015-10-06T15:47:00+00:00" 534 | }, 535 | { 536 | "name": "phpunit/php-file-iterator", 537 | "version": "1.4.5", 538 | "source": { 539 | "type": "git", 540 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 541 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" 542 | }, 543 | "dist": { 544 | "type": "zip", 545 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", 546 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", 547 | "shasum": "" 548 | }, 549 | "require": { 550 | "php": ">=5.3.3" 551 | }, 552 | "type": "library", 553 | "extra": { 554 | "branch-alias": { 555 | "dev-master": "1.4.x-dev" 556 | } 557 | }, 558 | "autoload": { 559 | "classmap": [ 560 | "src/" 561 | ] 562 | }, 563 | "notification-url": "https://packagist.org/downloads/", 564 | "license": [ 565 | "BSD-3-Clause" 566 | ], 567 | "authors": [ 568 | { 569 | "name": "Sebastian Bergmann", 570 | "email": "sb@sebastian-bergmann.de", 571 | "role": "lead" 572 | } 573 | ], 574 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 575 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 576 | "keywords": [ 577 | "filesystem", 578 | "iterator" 579 | ], 580 | "time": "2017-11-27T13:52:08+00:00" 581 | }, 582 | { 583 | "name": "phpunit/php-text-template", 584 | "version": "1.2.1", 585 | "source": { 586 | "type": "git", 587 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 588 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 589 | }, 590 | "dist": { 591 | "type": "zip", 592 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 593 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 594 | "shasum": "" 595 | }, 596 | "require": { 597 | "php": ">=5.3.3" 598 | }, 599 | "type": "library", 600 | "autoload": { 601 | "classmap": [ 602 | "src/" 603 | ] 604 | }, 605 | "notification-url": "https://packagist.org/downloads/", 606 | "license": [ 607 | "BSD-3-Clause" 608 | ], 609 | "authors": [ 610 | { 611 | "name": "Sebastian Bergmann", 612 | "email": "sebastian@phpunit.de", 613 | "role": "lead" 614 | } 615 | ], 616 | "description": "Simple template engine.", 617 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 618 | "keywords": [ 619 | "template" 620 | ], 621 | "time": "2015-06-21T13:50:34+00:00" 622 | }, 623 | { 624 | "name": "phpunit/php-timer", 625 | "version": "1.0.9", 626 | "source": { 627 | "type": "git", 628 | "url": "https://github.com/sebastianbergmann/php-timer.git", 629 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 630 | }, 631 | "dist": { 632 | "type": "zip", 633 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 634 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 635 | "shasum": "" 636 | }, 637 | "require": { 638 | "php": "^5.3.3 || ^7.0" 639 | }, 640 | "require-dev": { 641 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 642 | }, 643 | "type": "library", 644 | "extra": { 645 | "branch-alias": { 646 | "dev-master": "1.0-dev" 647 | } 648 | }, 649 | "autoload": { 650 | "classmap": [ 651 | "src/" 652 | ] 653 | }, 654 | "notification-url": "https://packagist.org/downloads/", 655 | "license": [ 656 | "BSD-3-Clause" 657 | ], 658 | "authors": [ 659 | { 660 | "name": "Sebastian Bergmann", 661 | "email": "sb@sebastian-bergmann.de", 662 | "role": "lead" 663 | } 664 | ], 665 | "description": "Utility class for timing", 666 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 667 | "keywords": [ 668 | "timer" 669 | ], 670 | "time": "2017-02-26T11:10:40+00:00" 671 | }, 672 | { 673 | "name": "phpunit/php-token-stream", 674 | "version": "1.4.12", 675 | "source": { 676 | "type": "git", 677 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 678 | "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" 679 | }, 680 | "dist": { 681 | "type": "zip", 682 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", 683 | "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", 684 | "shasum": "" 685 | }, 686 | "require": { 687 | "ext-tokenizer": "*", 688 | "php": ">=5.3.3" 689 | }, 690 | "require-dev": { 691 | "phpunit/phpunit": "~4.2" 692 | }, 693 | "type": "library", 694 | "extra": { 695 | "branch-alias": { 696 | "dev-master": "1.4-dev" 697 | } 698 | }, 699 | "autoload": { 700 | "classmap": [ 701 | "src/" 702 | ] 703 | }, 704 | "notification-url": "https://packagist.org/downloads/", 705 | "license": [ 706 | "BSD-3-Clause" 707 | ], 708 | "authors": [ 709 | { 710 | "name": "Sebastian Bergmann", 711 | "email": "sebastian@phpunit.de" 712 | } 713 | ], 714 | "description": "Wrapper around PHP's tokenizer extension.", 715 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 716 | "keywords": [ 717 | "tokenizer" 718 | ], 719 | "time": "2017-12-04T08:55:13+00:00" 720 | }, 721 | { 722 | "name": "phpunit/phpunit", 723 | "version": "4.8.36", 724 | "source": { 725 | "type": "git", 726 | "url": "https://github.com/sebastianbergmann/phpunit.git", 727 | "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" 728 | }, 729 | "dist": { 730 | "type": "zip", 731 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", 732 | "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", 733 | "shasum": "" 734 | }, 735 | "require": { 736 | "ext-dom": "*", 737 | "ext-json": "*", 738 | "ext-pcre": "*", 739 | "ext-reflection": "*", 740 | "ext-spl": "*", 741 | "php": ">=5.3.3", 742 | "phpspec/prophecy": "^1.3.1", 743 | "phpunit/php-code-coverage": "~2.1", 744 | "phpunit/php-file-iterator": "~1.4", 745 | "phpunit/php-text-template": "~1.2", 746 | "phpunit/php-timer": "^1.0.6", 747 | "phpunit/phpunit-mock-objects": "~2.3", 748 | "sebastian/comparator": "~1.2.2", 749 | "sebastian/diff": "~1.2", 750 | "sebastian/environment": "~1.3", 751 | "sebastian/exporter": "~1.2", 752 | "sebastian/global-state": "~1.0", 753 | "sebastian/version": "~1.0", 754 | "symfony/yaml": "~2.1|~3.0" 755 | }, 756 | "suggest": { 757 | "phpunit/php-invoker": "~1.1" 758 | }, 759 | "bin": [ 760 | "phpunit" 761 | ], 762 | "type": "library", 763 | "extra": { 764 | "branch-alias": { 765 | "dev-master": "4.8.x-dev" 766 | } 767 | }, 768 | "autoload": { 769 | "classmap": [ 770 | "src/" 771 | ] 772 | }, 773 | "notification-url": "https://packagist.org/downloads/", 774 | "license": [ 775 | "BSD-3-Clause" 776 | ], 777 | "authors": [ 778 | { 779 | "name": "Sebastian Bergmann", 780 | "email": "sebastian@phpunit.de", 781 | "role": "lead" 782 | } 783 | ], 784 | "description": "The PHP Unit Testing framework.", 785 | "homepage": "https://phpunit.de/", 786 | "keywords": [ 787 | "phpunit", 788 | "testing", 789 | "xunit" 790 | ], 791 | "time": "2017-06-21T08:07:12+00:00" 792 | }, 793 | { 794 | "name": "phpunit/phpunit-mock-objects", 795 | "version": "2.3.8", 796 | "source": { 797 | "type": "git", 798 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 799 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" 800 | }, 801 | "dist": { 802 | "type": "zip", 803 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", 804 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", 805 | "shasum": "" 806 | }, 807 | "require": { 808 | "doctrine/instantiator": "^1.0.2", 809 | "php": ">=5.3.3", 810 | "phpunit/php-text-template": "~1.2", 811 | "sebastian/exporter": "~1.2" 812 | }, 813 | "require-dev": { 814 | "phpunit/phpunit": "~4.4" 815 | }, 816 | "suggest": { 817 | "ext-soap": "*" 818 | }, 819 | "type": "library", 820 | "extra": { 821 | "branch-alias": { 822 | "dev-master": "2.3.x-dev" 823 | } 824 | }, 825 | "autoload": { 826 | "classmap": [ 827 | "src/" 828 | ] 829 | }, 830 | "notification-url": "https://packagist.org/downloads/", 831 | "license": [ 832 | "BSD-3-Clause" 833 | ], 834 | "authors": [ 835 | { 836 | "name": "Sebastian Bergmann", 837 | "email": "sb@sebastian-bergmann.de", 838 | "role": "lead" 839 | } 840 | ], 841 | "description": "Mock Object library for PHPUnit", 842 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 843 | "keywords": [ 844 | "mock", 845 | "xunit" 846 | ], 847 | "abandoned": true, 848 | "time": "2015-10-02T06:51:40+00:00" 849 | }, 850 | { 851 | "name": "psr/log", 852 | "version": "1.1.0", 853 | "source": { 854 | "type": "git", 855 | "url": "https://github.com/php-fig/log.git", 856 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" 857 | }, 858 | "dist": { 859 | "type": "zip", 860 | "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", 861 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", 862 | "shasum": "" 863 | }, 864 | "require": { 865 | "php": ">=5.3.0" 866 | }, 867 | "type": "library", 868 | "extra": { 869 | "branch-alias": { 870 | "dev-master": "1.0.x-dev" 871 | } 872 | }, 873 | "autoload": { 874 | "psr-4": { 875 | "Psr\\Log\\": "Psr/Log/" 876 | } 877 | }, 878 | "notification-url": "https://packagist.org/downloads/", 879 | "license": [ 880 | "MIT" 881 | ], 882 | "authors": [ 883 | { 884 | "name": "PHP-FIG", 885 | "homepage": "http://www.php-fig.org/" 886 | } 887 | ], 888 | "description": "Common interface for logging libraries", 889 | "homepage": "https://github.com/php-fig/log", 890 | "keywords": [ 891 | "log", 892 | "psr", 893 | "psr-3" 894 | ], 895 | "time": "2018-11-20T15:27:04+00:00" 896 | }, 897 | { 898 | "name": "sebastian/comparator", 899 | "version": "1.2.4", 900 | "source": { 901 | "type": "git", 902 | "url": "https://github.com/sebastianbergmann/comparator.git", 903 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" 904 | }, 905 | "dist": { 906 | "type": "zip", 907 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 908 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 909 | "shasum": "" 910 | }, 911 | "require": { 912 | "php": ">=5.3.3", 913 | "sebastian/diff": "~1.2", 914 | "sebastian/exporter": "~1.2 || ~2.0" 915 | }, 916 | "require-dev": { 917 | "phpunit/phpunit": "~4.4" 918 | }, 919 | "type": "library", 920 | "extra": { 921 | "branch-alias": { 922 | "dev-master": "1.2.x-dev" 923 | } 924 | }, 925 | "autoload": { 926 | "classmap": [ 927 | "src/" 928 | ] 929 | }, 930 | "notification-url": "https://packagist.org/downloads/", 931 | "license": [ 932 | "BSD-3-Clause" 933 | ], 934 | "authors": [ 935 | { 936 | "name": "Jeff Welch", 937 | "email": "whatthejeff@gmail.com" 938 | }, 939 | { 940 | "name": "Volker Dusch", 941 | "email": "github@wallbash.com" 942 | }, 943 | { 944 | "name": "Bernhard Schussek", 945 | "email": "bschussek@2bepublished.at" 946 | }, 947 | { 948 | "name": "Sebastian Bergmann", 949 | "email": "sebastian@phpunit.de" 950 | } 951 | ], 952 | "description": "Provides the functionality to compare PHP values for equality", 953 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 954 | "keywords": [ 955 | "comparator", 956 | "compare", 957 | "equality" 958 | ], 959 | "time": "2017-01-29T09:50:25+00:00" 960 | }, 961 | { 962 | "name": "sebastian/diff", 963 | "version": "1.4.3", 964 | "source": { 965 | "type": "git", 966 | "url": "https://github.com/sebastianbergmann/diff.git", 967 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" 968 | }, 969 | "dist": { 970 | "type": "zip", 971 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", 972 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", 973 | "shasum": "" 974 | }, 975 | "require": { 976 | "php": "^5.3.3 || ^7.0" 977 | }, 978 | "require-dev": { 979 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 980 | }, 981 | "type": "library", 982 | "extra": { 983 | "branch-alias": { 984 | "dev-master": "1.4-dev" 985 | } 986 | }, 987 | "autoload": { 988 | "classmap": [ 989 | "src/" 990 | ] 991 | }, 992 | "notification-url": "https://packagist.org/downloads/", 993 | "license": [ 994 | "BSD-3-Clause" 995 | ], 996 | "authors": [ 997 | { 998 | "name": "Kore Nordmann", 999 | "email": "mail@kore-nordmann.de" 1000 | }, 1001 | { 1002 | "name": "Sebastian Bergmann", 1003 | "email": "sebastian@phpunit.de" 1004 | } 1005 | ], 1006 | "description": "Diff implementation", 1007 | "homepage": "https://github.com/sebastianbergmann/diff", 1008 | "keywords": [ 1009 | "diff" 1010 | ], 1011 | "time": "2017-05-22T07:24:03+00:00" 1012 | }, 1013 | { 1014 | "name": "sebastian/environment", 1015 | "version": "1.3.8", 1016 | "source": { 1017 | "type": "git", 1018 | "url": "https://github.com/sebastianbergmann/environment.git", 1019 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" 1020 | }, 1021 | "dist": { 1022 | "type": "zip", 1023 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", 1024 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", 1025 | "shasum": "" 1026 | }, 1027 | "require": { 1028 | "php": "^5.3.3 || ^7.0" 1029 | }, 1030 | "require-dev": { 1031 | "phpunit/phpunit": "^4.8 || ^5.0" 1032 | }, 1033 | "type": "library", 1034 | "extra": { 1035 | "branch-alias": { 1036 | "dev-master": "1.3.x-dev" 1037 | } 1038 | }, 1039 | "autoload": { 1040 | "classmap": [ 1041 | "src/" 1042 | ] 1043 | }, 1044 | "notification-url": "https://packagist.org/downloads/", 1045 | "license": [ 1046 | "BSD-3-Clause" 1047 | ], 1048 | "authors": [ 1049 | { 1050 | "name": "Sebastian Bergmann", 1051 | "email": "sebastian@phpunit.de" 1052 | } 1053 | ], 1054 | "description": "Provides functionality to handle HHVM/PHP environments", 1055 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1056 | "keywords": [ 1057 | "Xdebug", 1058 | "environment", 1059 | "hhvm" 1060 | ], 1061 | "time": "2016-08-18T05:49:44+00:00" 1062 | }, 1063 | { 1064 | "name": "sebastian/exporter", 1065 | "version": "1.2.2", 1066 | "source": { 1067 | "type": "git", 1068 | "url": "https://github.com/sebastianbergmann/exporter.git", 1069 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" 1070 | }, 1071 | "dist": { 1072 | "type": "zip", 1073 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", 1074 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", 1075 | "shasum": "" 1076 | }, 1077 | "require": { 1078 | "php": ">=5.3.3", 1079 | "sebastian/recursion-context": "~1.0" 1080 | }, 1081 | "require-dev": { 1082 | "ext-mbstring": "*", 1083 | "phpunit/phpunit": "~4.4" 1084 | }, 1085 | "type": "library", 1086 | "extra": { 1087 | "branch-alias": { 1088 | "dev-master": "1.3.x-dev" 1089 | } 1090 | }, 1091 | "autoload": { 1092 | "classmap": [ 1093 | "src/" 1094 | ] 1095 | }, 1096 | "notification-url": "https://packagist.org/downloads/", 1097 | "license": [ 1098 | "BSD-3-Clause" 1099 | ], 1100 | "authors": [ 1101 | { 1102 | "name": "Jeff Welch", 1103 | "email": "whatthejeff@gmail.com" 1104 | }, 1105 | { 1106 | "name": "Volker Dusch", 1107 | "email": "github@wallbash.com" 1108 | }, 1109 | { 1110 | "name": "Bernhard Schussek", 1111 | "email": "bschussek@2bepublished.at" 1112 | }, 1113 | { 1114 | "name": "Sebastian Bergmann", 1115 | "email": "sebastian@phpunit.de" 1116 | }, 1117 | { 1118 | "name": "Adam Harvey", 1119 | "email": "aharvey@php.net" 1120 | } 1121 | ], 1122 | "description": "Provides the functionality to export PHP variables for visualization", 1123 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1124 | "keywords": [ 1125 | "export", 1126 | "exporter" 1127 | ], 1128 | "time": "2016-06-17T09:04:28+00:00" 1129 | }, 1130 | { 1131 | "name": "sebastian/global-state", 1132 | "version": "1.1.1", 1133 | "source": { 1134 | "type": "git", 1135 | "url": "https://github.com/sebastianbergmann/global-state.git", 1136 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 1137 | }, 1138 | "dist": { 1139 | "type": "zip", 1140 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 1141 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 1142 | "shasum": "" 1143 | }, 1144 | "require": { 1145 | "php": ">=5.3.3" 1146 | }, 1147 | "require-dev": { 1148 | "phpunit/phpunit": "~4.2" 1149 | }, 1150 | "suggest": { 1151 | "ext-uopz": "*" 1152 | }, 1153 | "type": "library", 1154 | "extra": { 1155 | "branch-alias": { 1156 | "dev-master": "1.0-dev" 1157 | } 1158 | }, 1159 | "autoload": { 1160 | "classmap": [ 1161 | "src/" 1162 | ] 1163 | }, 1164 | "notification-url": "https://packagist.org/downloads/", 1165 | "license": [ 1166 | "BSD-3-Clause" 1167 | ], 1168 | "authors": [ 1169 | { 1170 | "name": "Sebastian Bergmann", 1171 | "email": "sebastian@phpunit.de" 1172 | } 1173 | ], 1174 | "description": "Snapshotting of global state", 1175 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1176 | "keywords": [ 1177 | "global state" 1178 | ], 1179 | "time": "2015-10-12T03:26:01+00:00" 1180 | }, 1181 | { 1182 | "name": "sebastian/recursion-context", 1183 | "version": "1.0.5", 1184 | "source": { 1185 | "type": "git", 1186 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1187 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" 1188 | }, 1189 | "dist": { 1190 | "type": "zip", 1191 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 1192 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 1193 | "shasum": "" 1194 | }, 1195 | "require": { 1196 | "php": ">=5.3.3" 1197 | }, 1198 | "require-dev": { 1199 | "phpunit/phpunit": "~4.4" 1200 | }, 1201 | "type": "library", 1202 | "extra": { 1203 | "branch-alias": { 1204 | "dev-master": "1.0.x-dev" 1205 | } 1206 | }, 1207 | "autoload": { 1208 | "classmap": [ 1209 | "src/" 1210 | ] 1211 | }, 1212 | "notification-url": "https://packagist.org/downloads/", 1213 | "license": [ 1214 | "BSD-3-Clause" 1215 | ], 1216 | "authors": [ 1217 | { 1218 | "name": "Jeff Welch", 1219 | "email": "whatthejeff@gmail.com" 1220 | }, 1221 | { 1222 | "name": "Sebastian Bergmann", 1223 | "email": "sebastian@phpunit.de" 1224 | }, 1225 | { 1226 | "name": "Adam Harvey", 1227 | "email": "aharvey@php.net" 1228 | } 1229 | ], 1230 | "description": "Provides functionality to recursively process PHP variables", 1231 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1232 | "time": "2016-10-03T07:41:43+00:00" 1233 | }, 1234 | { 1235 | "name": "sebastian/version", 1236 | "version": "1.0.6", 1237 | "source": { 1238 | "type": "git", 1239 | "url": "https://github.com/sebastianbergmann/version.git", 1240 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 1241 | }, 1242 | "dist": { 1243 | "type": "zip", 1244 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1245 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1246 | "shasum": "" 1247 | }, 1248 | "type": "library", 1249 | "autoload": { 1250 | "classmap": [ 1251 | "src/" 1252 | ] 1253 | }, 1254 | "notification-url": "https://packagist.org/downloads/", 1255 | "license": [ 1256 | "BSD-3-Clause" 1257 | ], 1258 | "authors": [ 1259 | { 1260 | "name": "Sebastian Bergmann", 1261 | "email": "sebastian@phpunit.de", 1262 | "role": "lead" 1263 | } 1264 | ], 1265 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1266 | "homepage": "https://github.com/sebastianbergmann/version", 1267 | "time": "2015-06-21T13:59:46+00:00" 1268 | }, 1269 | { 1270 | "name": "symfony/console", 1271 | "version": "v3.4.22", 1272 | "source": { 1273 | "type": "git", 1274 | "url": "https://github.com/symfony/console.git", 1275 | "reference": "069bf3f0e8f871a2169a06e43d9f3f03f355e9be" 1276 | }, 1277 | "dist": { 1278 | "type": "zip", 1279 | "url": "https://api.github.com/repos/symfony/console/zipball/069bf3f0e8f871a2169a06e43d9f3f03f355e9be", 1280 | "reference": "069bf3f0e8f871a2169a06e43d9f3f03f355e9be", 1281 | "shasum": "" 1282 | }, 1283 | "require": { 1284 | "php": "^5.5.9|>=7.0.8", 1285 | "symfony/debug": "~2.8|~3.0|~4.0", 1286 | "symfony/polyfill-mbstring": "~1.0" 1287 | }, 1288 | "conflict": { 1289 | "symfony/dependency-injection": "<3.4", 1290 | "symfony/process": "<3.3" 1291 | }, 1292 | "provide": { 1293 | "psr/log-implementation": "1.0" 1294 | }, 1295 | "require-dev": { 1296 | "psr/log": "~1.0", 1297 | "symfony/config": "~3.3|~4.0", 1298 | "symfony/dependency-injection": "~3.4|~4.0", 1299 | "symfony/event-dispatcher": "~2.8|~3.0|~4.0", 1300 | "symfony/lock": "~3.4|~4.0", 1301 | "symfony/process": "~3.3|~4.0" 1302 | }, 1303 | "suggest": { 1304 | "psr/log": "For using the console logger", 1305 | "symfony/event-dispatcher": "", 1306 | "symfony/lock": "", 1307 | "symfony/process": "" 1308 | }, 1309 | "type": "library", 1310 | "extra": { 1311 | "branch-alias": { 1312 | "dev-master": "3.4-dev" 1313 | } 1314 | }, 1315 | "autoload": { 1316 | "psr-4": { 1317 | "Symfony\\Component\\Console\\": "" 1318 | }, 1319 | "exclude-from-classmap": [ 1320 | "/Tests/" 1321 | ] 1322 | }, 1323 | "notification-url": "https://packagist.org/downloads/", 1324 | "license": [ 1325 | "MIT" 1326 | ], 1327 | "authors": [ 1328 | { 1329 | "name": "Fabien Potencier", 1330 | "email": "fabien@symfony.com" 1331 | }, 1332 | { 1333 | "name": "Symfony Community", 1334 | "homepage": "https://symfony.com/contributors" 1335 | } 1336 | ], 1337 | "description": "Symfony Console Component", 1338 | "homepage": "https://symfony.com", 1339 | "time": "2019-01-25T10:42:12+00:00" 1340 | }, 1341 | { 1342 | "name": "symfony/debug", 1343 | "version": "v3.4.22", 1344 | "source": { 1345 | "type": "git", 1346 | "url": "https://github.com/symfony/debug.git", 1347 | "reference": "667a26c4dd6bc75c67f06bc9bcd015bdecc7cbb8" 1348 | }, 1349 | "dist": { 1350 | "type": "zip", 1351 | "url": "https://api.github.com/repos/symfony/debug/zipball/667a26c4dd6bc75c67f06bc9bcd015bdecc7cbb8", 1352 | "reference": "667a26c4dd6bc75c67f06bc9bcd015bdecc7cbb8", 1353 | "shasum": "" 1354 | }, 1355 | "require": { 1356 | "php": "^5.5.9|>=7.0.8", 1357 | "psr/log": "~1.0" 1358 | }, 1359 | "conflict": { 1360 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" 1361 | }, 1362 | "require-dev": { 1363 | "symfony/http-kernel": "~2.8|~3.0|~4.0" 1364 | }, 1365 | "type": "library", 1366 | "extra": { 1367 | "branch-alias": { 1368 | "dev-master": "3.4-dev" 1369 | } 1370 | }, 1371 | "autoload": { 1372 | "psr-4": { 1373 | "Symfony\\Component\\Debug\\": "" 1374 | }, 1375 | "exclude-from-classmap": [ 1376 | "/Tests/" 1377 | ] 1378 | }, 1379 | "notification-url": "https://packagist.org/downloads/", 1380 | "license": [ 1381 | "MIT" 1382 | ], 1383 | "authors": [ 1384 | { 1385 | "name": "Fabien Potencier", 1386 | "email": "fabien@symfony.com" 1387 | }, 1388 | { 1389 | "name": "Symfony Community", 1390 | "homepage": "https://symfony.com/contributors" 1391 | } 1392 | ], 1393 | "description": "Symfony Debug Component", 1394 | "homepage": "https://symfony.com", 1395 | "time": "2019-01-25T10:19:25+00:00" 1396 | }, 1397 | { 1398 | "name": "symfony/event-dispatcher", 1399 | "version": "v3.4.22", 1400 | "source": { 1401 | "type": "git", 1402 | "url": "https://github.com/symfony/event-dispatcher.git", 1403 | "reference": "ed5be1663fa66623b3a7004d5d51a14c4045399b" 1404 | }, 1405 | "dist": { 1406 | "type": "zip", 1407 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ed5be1663fa66623b3a7004d5d51a14c4045399b", 1408 | "reference": "ed5be1663fa66623b3a7004d5d51a14c4045399b", 1409 | "shasum": "" 1410 | }, 1411 | "require": { 1412 | "php": "^5.5.9|>=7.0.8" 1413 | }, 1414 | "conflict": { 1415 | "symfony/dependency-injection": "<3.3" 1416 | }, 1417 | "require-dev": { 1418 | "psr/log": "~1.0", 1419 | "symfony/config": "~2.8|~3.0|~4.0", 1420 | "symfony/dependency-injection": "~3.3|~4.0", 1421 | "symfony/expression-language": "~2.8|~3.0|~4.0", 1422 | "symfony/stopwatch": "~2.8|~3.0|~4.0" 1423 | }, 1424 | "suggest": { 1425 | "symfony/dependency-injection": "", 1426 | "symfony/http-kernel": "" 1427 | }, 1428 | "type": "library", 1429 | "extra": { 1430 | "branch-alias": { 1431 | "dev-master": "3.4-dev" 1432 | } 1433 | }, 1434 | "autoload": { 1435 | "psr-4": { 1436 | "Symfony\\Component\\EventDispatcher\\": "" 1437 | }, 1438 | "exclude-from-classmap": [ 1439 | "/Tests/" 1440 | ] 1441 | }, 1442 | "notification-url": "https://packagist.org/downloads/", 1443 | "license": [ 1444 | "MIT" 1445 | ], 1446 | "authors": [ 1447 | { 1448 | "name": "Fabien Potencier", 1449 | "email": "fabien@symfony.com" 1450 | }, 1451 | { 1452 | "name": "Symfony Community", 1453 | "homepage": "https://symfony.com/contributors" 1454 | } 1455 | ], 1456 | "description": "Symfony EventDispatcher Component", 1457 | "homepage": "https://symfony.com", 1458 | "time": "2019-01-16T13:27:11+00:00" 1459 | }, 1460 | { 1461 | "name": "symfony/filesystem", 1462 | "version": "v3.4.22", 1463 | "source": { 1464 | "type": "git", 1465 | "url": "https://github.com/symfony/filesystem.git", 1466 | "reference": "b52454ec66fe5082b7a66a491339d1f1da9a5a0d" 1467 | }, 1468 | "dist": { 1469 | "type": "zip", 1470 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/b52454ec66fe5082b7a66a491339d1f1da9a5a0d", 1471 | "reference": "b52454ec66fe5082b7a66a491339d1f1da9a5a0d", 1472 | "shasum": "" 1473 | }, 1474 | "require": { 1475 | "php": "^5.5.9|>=7.0.8", 1476 | "symfony/polyfill-ctype": "~1.8" 1477 | }, 1478 | "type": "library", 1479 | "extra": { 1480 | "branch-alias": { 1481 | "dev-master": "3.4-dev" 1482 | } 1483 | }, 1484 | "autoload": { 1485 | "psr-4": { 1486 | "Symfony\\Component\\Filesystem\\": "" 1487 | }, 1488 | "exclude-from-classmap": [ 1489 | "/Tests/" 1490 | ] 1491 | }, 1492 | "notification-url": "https://packagist.org/downloads/", 1493 | "license": [ 1494 | "MIT" 1495 | ], 1496 | "authors": [ 1497 | { 1498 | "name": "Fabien Potencier", 1499 | "email": "fabien@symfony.com" 1500 | }, 1501 | { 1502 | "name": "Symfony Community", 1503 | "homepage": "https://symfony.com/contributors" 1504 | } 1505 | ], 1506 | "description": "Symfony Filesystem Component", 1507 | "homepage": "https://symfony.com", 1508 | "time": "2019-01-16T13:27:11+00:00" 1509 | }, 1510 | { 1511 | "name": "symfony/finder", 1512 | "version": "v3.4.22", 1513 | "source": { 1514 | "type": "git", 1515 | "url": "https://github.com/symfony/finder.git", 1516 | "reference": "7c0c627220308928e958a87c293108e5891cde1d" 1517 | }, 1518 | "dist": { 1519 | "type": "zip", 1520 | "url": "https://api.github.com/repos/symfony/finder/zipball/7c0c627220308928e958a87c293108e5891cde1d", 1521 | "reference": "7c0c627220308928e958a87c293108e5891cde1d", 1522 | "shasum": "" 1523 | }, 1524 | "require": { 1525 | "php": "^5.5.9|>=7.0.8" 1526 | }, 1527 | "type": "library", 1528 | "extra": { 1529 | "branch-alias": { 1530 | "dev-master": "3.4-dev" 1531 | } 1532 | }, 1533 | "autoload": { 1534 | "psr-4": { 1535 | "Symfony\\Component\\Finder\\": "" 1536 | }, 1537 | "exclude-from-classmap": [ 1538 | "/Tests/" 1539 | ] 1540 | }, 1541 | "notification-url": "https://packagist.org/downloads/", 1542 | "license": [ 1543 | "MIT" 1544 | ], 1545 | "authors": [ 1546 | { 1547 | "name": "Fabien Potencier", 1548 | "email": "fabien@symfony.com" 1549 | }, 1550 | { 1551 | "name": "Symfony Community", 1552 | "homepage": "https://symfony.com/contributors" 1553 | } 1554 | ], 1555 | "description": "Symfony Finder Component", 1556 | "homepage": "https://symfony.com", 1557 | "time": "2019-01-16T13:43:35+00:00" 1558 | }, 1559 | { 1560 | "name": "symfony/polyfill-ctype", 1561 | "version": "v1.10.0", 1562 | "source": { 1563 | "type": "git", 1564 | "url": "https://github.com/symfony/polyfill-ctype.git", 1565 | "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" 1566 | }, 1567 | "dist": { 1568 | "type": "zip", 1569 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", 1570 | "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", 1571 | "shasum": "" 1572 | }, 1573 | "require": { 1574 | "php": ">=5.3.3" 1575 | }, 1576 | "suggest": { 1577 | "ext-ctype": "For best performance" 1578 | }, 1579 | "type": "library", 1580 | "extra": { 1581 | "branch-alias": { 1582 | "dev-master": "1.9-dev" 1583 | } 1584 | }, 1585 | "autoload": { 1586 | "psr-4": { 1587 | "Symfony\\Polyfill\\Ctype\\": "" 1588 | }, 1589 | "files": [ 1590 | "bootstrap.php" 1591 | ] 1592 | }, 1593 | "notification-url": "https://packagist.org/downloads/", 1594 | "license": [ 1595 | "MIT" 1596 | ], 1597 | "authors": [ 1598 | { 1599 | "name": "Symfony Community", 1600 | "homepage": "https://symfony.com/contributors" 1601 | }, 1602 | { 1603 | "name": "Gert de Pagter", 1604 | "email": "BackEndTea@gmail.com" 1605 | } 1606 | ], 1607 | "description": "Symfony polyfill for ctype functions", 1608 | "homepage": "https://symfony.com", 1609 | "keywords": [ 1610 | "compatibility", 1611 | "ctype", 1612 | "polyfill", 1613 | "portable" 1614 | ], 1615 | "time": "2018-08-06T14:22:27+00:00" 1616 | }, 1617 | { 1618 | "name": "symfony/polyfill-mbstring", 1619 | "version": "v1.10.0", 1620 | "source": { 1621 | "type": "git", 1622 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1623 | "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" 1624 | }, 1625 | "dist": { 1626 | "type": "zip", 1627 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", 1628 | "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", 1629 | "shasum": "" 1630 | }, 1631 | "require": { 1632 | "php": ">=5.3.3" 1633 | }, 1634 | "suggest": { 1635 | "ext-mbstring": "For best performance" 1636 | }, 1637 | "type": "library", 1638 | "extra": { 1639 | "branch-alias": { 1640 | "dev-master": "1.9-dev" 1641 | } 1642 | }, 1643 | "autoload": { 1644 | "psr-4": { 1645 | "Symfony\\Polyfill\\Mbstring\\": "" 1646 | }, 1647 | "files": [ 1648 | "bootstrap.php" 1649 | ] 1650 | }, 1651 | "notification-url": "https://packagist.org/downloads/", 1652 | "license": [ 1653 | "MIT" 1654 | ], 1655 | "authors": [ 1656 | { 1657 | "name": "Nicolas Grekas", 1658 | "email": "p@tchwork.com" 1659 | }, 1660 | { 1661 | "name": "Symfony Community", 1662 | "homepage": "https://symfony.com/contributors" 1663 | } 1664 | ], 1665 | "description": "Symfony polyfill for the Mbstring extension", 1666 | "homepage": "https://symfony.com", 1667 | "keywords": [ 1668 | "compatibility", 1669 | "mbstring", 1670 | "polyfill", 1671 | "portable", 1672 | "shim" 1673 | ], 1674 | "time": "2018-09-21T13:07:52+00:00" 1675 | }, 1676 | { 1677 | "name": "symfony/process", 1678 | "version": "v3.4.22", 1679 | "source": { 1680 | "type": "git", 1681 | "url": "https://github.com/symfony/process.git", 1682 | "reference": "009f8dda80930e89e8344a4e310b08f9ff07dd2e" 1683 | }, 1684 | "dist": { 1685 | "type": "zip", 1686 | "url": "https://api.github.com/repos/symfony/process/zipball/009f8dda80930e89e8344a4e310b08f9ff07dd2e", 1687 | "reference": "009f8dda80930e89e8344a4e310b08f9ff07dd2e", 1688 | "shasum": "" 1689 | }, 1690 | "require": { 1691 | "php": "^5.5.9|>=7.0.8" 1692 | }, 1693 | "type": "library", 1694 | "extra": { 1695 | "branch-alias": { 1696 | "dev-master": "3.4-dev" 1697 | } 1698 | }, 1699 | "autoload": { 1700 | "psr-4": { 1701 | "Symfony\\Component\\Process\\": "" 1702 | }, 1703 | "exclude-from-classmap": [ 1704 | "/Tests/" 1705 | ] 1706 | }, 1707 | "notification-url": "https://packagist.org/downloads/", 1708 | "license": [ 1709 | "MIT" 1710 | ], 1711 | "authors": [ 1712 | { 1713 | "name": "Fabien Potencier", 1714 | "email": "fabien@symfony.com" 1715 | }, 1716 | { 1717 | "name": "Symfony Community", 1718 | "homepage": "https://symfony.com/contributors" 1719 | } 1720 | ], 1721 | "description": "Symfony Process Component", 1722 | "homepage": "https://symfony.com", 1723 | "time": "2019-01-16T13:27:11+00:00" 1724 | }, 1725 | { 1726 | "name": "symfony/stopwatch", 1727 | "version": "v3.4.22", 1728 | "source": { 1729 | "type": "git", 1730 | "url": "https://github.com/symfony/stopwatch.git", 1731 | "reference": "2a651c2645c10bbedd21170771f122d935e0dd58" 1732 | }, 1733 | "dist": { 1734 | "type": "zip", 1735 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2a651c2645c10bbedd21170771f122d935e0dd58", 1736 | "reference": "2a651c2645c10bbedd21170771f122d935e0dd58", 1737 | "shasum": "" 1738 | }, 1739 | "require": { 1740 | "php": "^5.5.9|>=7.0.8" 1741 | }, 1742 | "type": "library", 1743 | "extra": { 1744 | "branch-alias": { 1745 | "dev-master": "3.4-dev" 1746 | } 1747 | }, 1748 | "autoload": { 1749 | "psr-4": { 1750 | "Symfony\\Component\\Stopwatch\\": "" 1751 | }, 1752 | "exclude-from-classmap": [ 1753 | "/Tests/" 1754 | ] 1755 | }, 1756 | "notification-url": "https://packagist.org/downloads/", 1757 | "license": [ 1758 | "MIT" 1759 | ], 1760 | "authors": [ 1761 | { 1762 | "name": "Fabien Potencier", 1763 | "email": "fabien@symfony.com" 1764 | }, 1765 | { 1766 | "name": "Symfony Community", 1767 | "homepage": "https://symfony.com/contributors" 1768 | } 1769 | ], 1770 | "description": "Symfony Stopwatch Component", 1771 | "homepage": "https://symfony.com", 1772 | "time": "2019-01-16T09:39:14+00:00" 1773 | }, 1774 | { 1775 | "name": "symfony/yaml", 1776 | "version": "v3.4.22", 1777 | "source": { 1778 | "type": "git", 1779 | "url": "https://github.com/symfony/yaml.git", 1780 | "reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d" 1781 | }, 1782 | "dist": { 1783 | "type": "zip", 1784 | "url": "https://api.github.com/repos/symfony/yaml/zipball/ba11776e9e6c15ad5759a07bffb15899bac75c2d", 1785 | "reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d", 1786 | "shasum": "" 1787 | }, 1788 | "require": { 1789 | "php": "^5.5.9|>=7.0.8", 1790 | "symfony/polyfill-ctype": "~1.8" 1791 | }, 1792 | "conflict": { 1793 | "symfony/console": "<3.4" 1794 | }, 1795 | "require-dev": { 1796 | "symfony/console": "~3.4|~4.0" 1797 | }, 1798 | "suggest": { 1799 | "symfony/console": "For validating YAML files using the lint command" 1800 | }, 1801 | "type": "library", 1802 | "extra": { 1803 | "branch-alias": { 1804 | "dev-master": "3.4-dev" 1805 | } 1806 | }, 1807 | "autoload": { 1808 | "psr-4": { 1809 | "Symfony\\Component\\Yaml\\": "" 1810 | }, 1811 | "exclude-from-classmap": [ 1812 | "/Tests/" 1813 | ] 1814 | }, 1815 | "notification-url": "https://packagist.org/downloads/", 1816 | "license": [ 1817 | "MIT" 1818 | ], 1819 | "authors": [ 1820 | { 1821 | "name": "Fabien Potencier", 1822 | "email": "fabien@symfony.com" 1823 | }, 1824 | { 1825 | "name": "Symfony Community", 1826 | "homepage": "https://symfony.com/contributors" 1827 | } 1828 | ], 1829 | "description": "Symfony Yaml Component", 1830 | "homepage": "https://symfony.com", 1831 | "time": "2019-01-16T10:59:17+00:00" 1832 | }, 1833 | { 1834 | "name": "webmozart/assert", 1835 | "version": "1.4.0", 1836 | "source": { 1837 | "type": "git", 1838 | "url": "https://github.com/webmozart/assert.git", 1839 | "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" 1840 | }, 1841 | "dist": { 1842 | "type": "zip", 1843 | "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", 1844 | "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", 1845 | "shasum": "" 1846 | }, 1847 | "require": { 1848 | "php": "^5.3.3 || ^7.0", 1849 | "symfony/polyfill-ctype": "^1.8" 1850 | }, 1851 | "require-dev": { 1852 | "phpunit/phpunit": "^4.6", 1853 | "sebastian/version": "^1.0.1" 1854 | }, 1855 | "type": "library", 1856 | "extra": { 1857 | "branch-alias": { 1858 | "dev-master": "1.3-dev" 1859 | } 1860 | }, 1861 | "autoload": { 1862 | "psr-4": { 1863 | "Webmozart\\Assert\\": "src/" 1864 | } 1865 | }, 1866 | "notification-url": "https://packagist.org/downloads/", 1867 | "license": [ 1868 | "MIT" 1869 | ], 1870 | "authors": [ 1871 | { 1872 | "name": "Bernhard Schussek", 1873 | "email": "bschussek@gmail.com" 1874 | } 1875 | ], 1876 | "description": "Assertions to validate method input/output with nice error messages.", 1877 | "keywords": [ 1878 | "assert", 1879 | "check", 1880 | "validate" 1881 | ], 1882 | "time": "2018-12-25T11:19:39+00:00" 1883 | } 1884 | ], 1885 | "aliases": [], 1886 | "minimum-stability": "dev", 1887 | "stability-flags": [], 1888 | "prefer-stable": true, 1889 | "prefer-lowest": false, 1890 | "platform": { 1891 | "php": ">=5.5" 1892 | }, 1893 | "platform-dev": [] 1894 | } 1895 | -------------------------------------------------------------------------------- /lib/PHPTypes/State.php: -------------------------------------------------------------------------------- 1 | script = $script; 87 | foreach ($this->script->functions as $func) { 88 | if (!is_null($func->cfg)) { 89 | $this->blocks[] = $func->cfg; 90 | } 91 | } 92 | if (!is_null($this->script->main->cfg)) { 93 | $this->blocks[] = $this->script->main->cfg; 94 | } 95 | $this->resolver = new TypeResolver($this); 96 | $this->internalTypeInfo = new InternalArgInfo; 97 | $this->load(); 98 | } 99 | 100 | 101 | private function load() { 102 | $traverser = new Traverser; 103 | $declarations = new Visitor\DeclarationFinder; 104 | $calls = new Visitor\CallFinder; 105 | $variables = new Visitor\VariableFinder; 106 | $traverser->addVisitor($declarations); 107 | $traverser->addVisitor($calls); 108 | $traverser->addVisitor($variables); 109 | 110 | $this->script = $traverser->traverse($this->script); 111 | 112 | $this->variables = $variables->getVariables(); 113 | $this->constants = $declarations->getConstants(); 114 | $this->traits = $declarations->getTraits(); 115 | $this->classes = $declarations->getClasses(); 116 | $this->interfaces = $declarations->getInterfaces(); 117 | $this->methods = $declarations->getMethods(); 118 | $this->functions = $declarations->getFunctions(); 119 | $this->functionLookup = $this->buildFunctionLookup($declarations->getFunctions()); 120 | $this->callFinder = $calls; 121 | $this->methodCalls = $this->findMethodCalls(); 122 | $this->newCalls = $this->findNewCalls(); 123 | $this->computeTypeMatrix(); 124 | } 125 | 126 | private function buildFunctionLookup(array $functions) { 127 | $lookup = []; 128 | foreach ($functions as $function) { 129 | assert($function->name instanceof Operand\Literal); 130 | $name = strtolower($function->name->value); 131 | if (!isset($lookup[$name])) { 132 | $lookup[$name] = []; 133 | } 134 | $lookup[$name][] = $function; 135 | } 136 | return $lookup; 137 | } 138 | 139 | private function computeTypeMatrix() { 140 | // TODO: This is dirty, and needs cleaning 141 | // A extends B 142 | $map = []; // a => [a, b], b => [b] 143 | $interfaceMap = []; 144 | $classMap = []; 145 | $toProcess = []; 146 | foreach ($this->interfaces as $interface) { 147 | $name = strtolower($interface->name->value); 148 | $map[$name] = [$name => $interface]; 149 | $interfaceMap[$name] = []; 150 | if ($interface->extends) { 151 | foreach ($interface->extends as $extends) { 152 | $sub = strtolower($extends->value); 153 | $interfaceMap[$name][] = $sub; 154 | $map[$sub][$name] = $interface; 155 | } 156 | } 157 | } 158 | foreach ($this->classes as $class) { 159 | $name = strtolower($class->name->value); 160 | $map[$name] = [$name => $class]; 161 | $classMap[$name] = [$name]; 162 | foreach ($class->implements as $interface) { 163 | $iname = strtolower($interface->value); 164 | $classMap[$name][] = $iname; 165 | $map[$iname][$name] = $class; 166 | if (isset($interfaceMap[$iname])) { 167 | foreach ($interfaceMap[$iname] as $sub) { 168 | $classMap[$name][] = $sub; 169 | $map[$sub][$name] = $class; 170 | } 171 | } 172 | } 173 | if ($class->extends) { 174 | $toProcess[] = [$name, strtolower($class->extends->value), $class]; 175 | } 176 | } 177 | foreach ($toProcess as $ext) { 178 | $name = $ext[0]; 179 | $extends = $ext[1]; 180 | $class = $ext[2]; 181 | if (isset($classMap[$extends])) { 182 | foreach ($classMap[$extends] as $mapped) { 183 | $map[$mapped][$name] = $class; 184 | } 185 | } else { 186 | echo "Could not find parent $extends\n"; 187 | } 188 | } 189 | $this->classResolves = $map; 190 | $this->classResolvedBy = []; 191 | foreach ($map as $child => $parent) { 192 | foreach ($parent as $name => $_) { 193 | if (!isset($this->classResolvedBy[$name])) { 194 | $this->classResolvedBy[$name] = []; 195 | } 196 | //allows iterating and looking udm_cat_path(agent, category) 197 | $this->classResolvedBy[$name][$child] = $child; 198 | } 199 | } 200 | } 201 | 202 | private function findNewCalls() { 203 | $newCalls = []; 204 | foreach ($this->blocks as $block) { 205 | $newCalls = $this->findTypedBlock("Expr_New", $block, $newCalls); 206 | } 207 | return $newCalls; 208 | } 209 | 210 | private function findMethodCalls() { 211 | $methodCalls = []; 212 | foreach ($this->blocks as $block) { 213 | $methodCalls = $this->findTypedBlock("Expr_MethodCall", $block, $methodCalls); 214 | } 215 | return $methodCalls; 216 | } 217 | 218 | protected function findTypedBlock($type, Block $block, $result = []) { 219 | $toProcess = new SplObjectStorage; 220 | $processed = new SplObjectStorage; 221 | $toProcess->attach($block); 222 | while (count($toProcess) > 0) { 223 | foreach ($toProcess as $block) { 224 | $toProcess->detach($block); 225 | $processed->attach($block); 226 | foreach ($block->children as $op) { 227 | if ($op->getType() === $type) { 228 | $result[] = $op; 229 | } 230 | foreach ($op->getSubBlocks() as $name) { 231 | $sub = $op->$name; 232 | if (is_null($sub)) { 233 | continue; 234 | } 235 | if (!is_array($sub)) { 236 | $sub = [$sub]; 237 | } 238 | foreach ($sub as $subb) { 239 | if (!$processed->contains($subb)) { 240 | $toProcess->attach($subb); 241 | } 242 | } 243 | } 244 | } 245 | } 246 | } 247 | return $result; 248 | } 249 | } -------------------------------------------------------------------------------- /lib/PHPTypes/Type.php: -------------------------------------------------------------------------------- 1 | self::TYPE_ARRAY, 33 | self::TYPE_UNION => self::TYPE_UNION, 34 | self::TYPE_INTERSECTION => self::TYPE_INTERSECTION, 35 | ]; 36 | 37 | /** 38 | * @var int 39 | */ 40 | public $type = 0; 41 | 42 | /** 43 | * @var Type[] 44 | */ 45 | public $subTypes = []; 46 | 47 | /** 48 | * @var string 49 | */ 50 | public $userType = ''; 51 | 52 | /** 53 | * @var Type[] 54 | */ 55 | private static $typeCache = []; 56 | 57 | /** 58 | * @return Type 59 | */ 60 | public static function unknown() { 61 | return self::makeCachedType(Type::TYPE_UNKNOWN); 62 | } 63 | /** 64 | * @return Type 65 | */ 66 | public static function int() { 67 | return self::makeCachedType(Type::TYPE_LONG); 68 | } 69 | /** 70 | * @return Type 71 | */ 72 | public static function float() { 73 | return self::makeCachedType(Type::TYPE_DOUBLE); 74 | } 75 | /** 76 | * @return Type 77 | */ 78 | public static function string() { 79 | return self::makeCachedType(Type::TYPE_STRING); 80 | } 81 | /** 82 | * @return Type 83 | */ 84 | public static function bool() { 85 | return self::makeCachedType(Type::TYPE_BOOLEAN); 86 | } 87 | /** 88 | * @return Type 89 | */ 90 | public static function null() { 91 | return self::makeCachedType(Type::TYPE_NULL); 92 | } 93 | /** 94 | * @return Type 95 | */ 96 | public static function object() { 97 | return self::makeCachedType(Type::TYPE_OBJECT); 98 | } 99 | 100 | /** 101 | * @param int $key 102 | * 103 | * @return Type 104 | */ 105 | private static function makeCachedType($key) { 106 | if (!isset(self::$typeCache[$key])) { 107 | self::$typeCache[$key] = new Type($key); 108 | } 109 | return self::$typeCache[$key]; 110 | } 111 | /** 112 | * @return Type 113 | */ 114 | public static function numeric() { 115 | if (!isset(self::$typeCache["numeric"])) { 116 | self::$typeCache["numeric"] = new Type(Type::TYPE_UNION, [self::int(), self::float()]); 117 | } 118 | return self::$typeCache["numeric"]; 119 | } 120 | /** 121 | * @return Type 122 | */ 123 | public static function mixed() { 124 | if (!isset(self::$typeCache["mixed"])) { 125 | $subs = []; 126 | foreach (self::getPrimitives() as $key => $name) { 127 | $subs[] = self::makeCachedType($key); 128 | } 129 | self::$typeCache["mixed"] = new Type(Type::TYPE_UNION, $subs); 130 | } 131 | return self::$typeCache["mixed"]; 132 | } 133 | 134 | /** 135 | * @param int $type 136 | * @param Type[] $subTypes 137 | * @param ?string $userType 138 | */ 139 | public function __construct($type, array $subTypes = [], $userType = null) { 140 | $this->type = $type; 141 | if ($type === self::TYPE_OBJECT) { 142 | $this->userType = (string) $userType; 143 | } elseif (isset(self::$hasSubtypes[$type])) { 144 | $this->subTypes = $subTypes; 145 | foreach ($subTypes as $sub) { 146 | if (!$sub instanceof Type) { 147 | throw new \RuntimeException("Sub types must implement Type"); 148 | } 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * Get the primitives 155 | * 156 | * @return string[] 157 | */ 158 | public static function getPrimitives() { 159 | return [ 160 | Type::TYPE_NULL => 'null', 161 | Type::TYPE_BOOLEAN => 'bool', 162 | Type::TYPE_LONG => 'int', 163 | Type::TYPE_DOUBLE => 'float', 164 | Type::TYPE_STRING => 'string', 165 | Type::TYPE_OBJECT => 'object', 166 | Type::TYPE_ARRAY => 'array', 167 | Type::TYPE_CALLABLE => 'callable', 168 | ]; 169 | } 170 | 171 | /** 172 | * @return string 173 | */ 174 | public function __toString() { 175 | static $ctr = 0; 176 | $ctr++; 177 | if ($this->type === Type::TYPE_UNKNOWN) { 178 | $ctr--; 179 | return "unknown"; 180 | } 181 | $primitives = self::getPrimitives(); 182 | if (isset($primitives[$this->type])) { 183 | $ctr--; 184 | if ($this->type === Type::TYPE_OBJECT && $this->userType) { 185 | return $this->userType; 186 | } elseif ($this->type === Type::TYPE_ARRAY && $this->subTypes) { 187 | return $this->subTypes[0] . '[]'; 188 | } 189 | return $primitives[$this->type]; 190 | } 191 | $value = ''; 192 | if ($this->type === Type::TYPE_UNION) { 193 | $value = implode('|', $this->subTypes); 194 | } elseif ($this->type === Type::TYPE_INTERSECTION) { 195 | $value = implode('&', $this->subTypes); 196 | } else { 197 | throw new \RuntimeException("Assertion failure: unknown type {$this->type}"); 198 | } 199 | $ctr--; 200 | if ($ctr > 0) { 201 | return '(' . $value . ')'; 202 | } 203 | return $value; 204 | } 205 | 206 | public function hasSubtypes() { 207 | return in_array($this->type, self::$hasSubtypes); 208 | } 209 | 210 | public function allowsNull() { 211 | if ($this->type === Type::TYPE_NULL) { 212 | return true; 213 | } 214 | if ($this->type === Type::TYPE_UNION) { 215 | foreach ($this->subTypes as $subType) { 216 | if ($subType->allowsNull()) { 217 | return true; 218 | } 219 | } 220 | } 221 | if ($this->type === Type::TYPE_INTERSECTION) { 222 | foreach ($this->subTypes as $subType) { 223 | if (!$subType->allowsNull()) { 224 | return false; 225 | } 226 | } 227 | } 228 | return false; 229 | } 230 | 231 | /** 232 | * @param string $kind 233 | * @param string $comment 234 | * @param string $name The name of the parameter 235 | * 236 | * @return Type The type 237 | */ 238 | public static function extractTypeFromComment($kind, $comment, $name = '') { 239 | $match = []; 240 | switch ($kind) { 241 | case 'var': 242 | if (preg_match('(@var\s+(\S+))', $comment, $match)) { 243 | $return = Type::fromDecl($match[1]); 244 | return $return; 245 | } 246 | break; 247 | case 'return': 248 | if (preg_match('(@return\s+(\S+))', $comment, $match)) { 249 | $return = Type::fromDecl($match[1]); 250 | return $return; 251 | } 252 | break; 253 | case 'param': 254 | if (preg_match("(@param\\s+(\\S+)\\s+\\\${$name})i", $comment, $match)) { 255 | $param = Type::fromDecl($match[1]); 256 | return $param; 257 | } 258 | break; 259 | } 260 | return self::mixed(); 261 | } 262 | 263 | /** 264 | * @return Type 265 | */ 266 | public function simplify() { 267 | if ($this->type !== Type::TYPE_UNION && $this->type !== Type::TYPE_INTERSECTION) { 268 | return $this; 269 | } 270 | $new = []; 271 | foreach ($this->subTypes as $subType) { 272 | $subType = $subType->simplify(); 273 | if ($this->type === $subType->type) { 274 | $new = array_merge($new, $subType->subTypes); 275 | } else { 276 | $new[] = $subType->simplify(); 277 | } 278 | } 279 | // TODO: compute redundant unions 280 | return (new Type($this->type, $new)); 281 | } 282 | 283 | /** 284 | * @param string $decl 285 | * 286 | * @return Type The type 287 | */ 288 | public static function fromDecl($decl) { 289 | if ($decl instanceof Type) { 290 | return $decl; 291 | } elseif (!is_string($decl)) { 292 | throw new \LogicException("Should never happen"); 293 | } elseif (empty($decl)) { 294 | throw new \RuntimeException("Empty declaration found"); 295 | } 296 | if ($decl[0] === '\\') { 297 | $decl = substr($decl, 1); 298 | } elseif ($decl[0] === '?') { 299 | $decl = substr($decl, 1); 300 | $type = Type::fromDecl($decl); 301 | return (new Type(Type::TYPE_UNION, [ 302 | $type, 303 | new Type(Type::TYPE_NULL) 304 | ]))->simplify(); 305 | } 306 | switch (strtolower($decl)) { 307 | case 'boolean': 308 | case 'bool': 309 | case 'false': 310 | case 'true': 311 | return new Type(Type::TYPE_BOOLEAN); 312 | case 'integer': 313 | case 'int': 314 | return new Type(Type::TYPE_LONG); 315 | case 'double': 316 | case 'real': 317 | case 'float': 318 | return new Type(Type::TYPE_DOUBLE); 319 | case 'string': 320 | return new Type(Type::TYPE_STRING); 321 | case 'array': 322 | return new Type(Type::TYPE_ARRAY); 323 | case 'callable': 324 | return new Type(Type::TYPE_CALLABLE); 325 | case 'null': 326 | case 'void': 327 | return new Type(Type::TYPE_NULL); 328 | case 'numeric': 329 | return Type::fromDecl('int|float'); 330 | } 331 | // TODO: parse | and & and () 332 | if (strpos($decl, '|') !== false || strpos($decl, '&') !== false || strpos($decl, '(') !== false) { 333 | return self::parseCompexDecl($decl)->simplify(); 334 | } 335 | if (substr($decl, -2) === '[]') { 336 | $type = Type::fromDecl(substr($decl, 0, -2)); 337 | return new Type(Type::TYPE_ARRAY, [$type]); 338 | } 339 | $regex = '(^([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\\)*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$)'; 340 | if (!preg_match($regex, $decl)) { 341 | throw new \RuntimeException("Unknown type declaration found: $decl"); 342 | } 343 | return new Type(Type::TYPE_OBJECT, [], $decl); 344 | } 345 | 346 | /** 347 | * @param string $decl 348 | * 349 | * @return Type 350 | */ 351 | private static function parseCompexDecl($decl) { 352 | $left = null; 353 | $right = null; 354 | $combinator = ''; 355 | if (substr($decl, 0, 1) === '(') { 356 | $regex = '(^(\(((?>[^()]+)|(?1))*\)))'; 357 | $match = []; 358 | if (preg_match($regex, $decl, $match)) { 359 | $sub = (string) $match[0]; 360 | $left = self::fromDecl(substr($sub, 1, -1)); 361 | if ($sub === $decl) { 362 | return $left; 363 | } 364 | $decl = substr($decl, strlen($sub)); 365 | } else { 366 | throw new \RuntimeException("Unmatched braces?"); 367 | } 368 | if (!in_array(substr($decl, 0, 1), ['|', '&'])) { 369 | throw new \RuntimeException("Unknown position of combinator: $decl"); 370 | } 371 | $right = self::fromDecl(substr($decl, 1)); 372 | $combinator = substr($decl, 0, 1); 373 | } else { 374 | $orPos = strpos($decl, '|'); 375 | $andPos = strpos($decl, '&'); 376 | $pos = 0; 377 | if ($orPos === false && $andPos !== false) { 378 | $pos = $andPos; 379 | } elseif ($orPos !== false && $andPos === false) { 380 | $pos = $orPos; 381 | } elseif ($orPos !== false && $andPos !== false) { 382 | $pos = min($orPos, $andPos); 383 | } else { 384 | throw new \RuntimeException("No combinator found: $decl"); 385 | } 386 | if ($pos === 0) { 387 | throw new \RuntimeException("Unknown position of combinator: $decl"); 388 | } 389 | $left = self::fromDecl(substr($decl, 0, $pos)); 390 | $right = self::fromDecl(substr($decl, $pos + 1)); 391 | $combinator = substr($decl, $pos, 1); 392 | } 393 | if ($combinator === '|') { 394 | return new Type(Type::TYPE_UNION, [$left, $right]); 395 | } elseif ($combinator === '&') { 396 | return new Type(Type::TYPE_INTERSECTION, [$left, $right]); 397 | } 398 | throw new \RuntimeException("Unknown combinator $combinator"); 399 | } 400 | 401 | /** 402 | * @param mixed $value 403 | * 404 | * @return Type The type 405 | */ 406 | public static function fromValue($value) { 407 | if (is_int($value)) { 408 | return new Type(Type::TYPE_LONG); 409 | } elseif (is_bool($value)) { 410 | return new Type(Type::TYPE_BOOLEAN); 411 | } elseif (is_double($value)) { 412 | return new Type(Type::TYPE_DOUBLE); 413 | } elseif (is_string($value)) { 414 | return new Type(Type::TYPE_STRING); 415 | } 416 | throw new \RuntimeException("Unknown value type found: " . gettype($value)); 417 | } 418 | 419 | /** 420 | * @param Type $type 421 | * 422 | * @return bool The status 423 | */ 424 | public function equals(Type $type) { 425 | if ($type === $this) { 426 | return true; 427 | } 428 | if ($type->type !== $this->type) { 429 | return false; 430 | } 431 | if ($type->type === Type::TYPE_OBJECT) { 432 | return strtolower($type->userType) === strtolower($this->userType); 433 | } 434 | // TODO: handle sub types 435 | if (isset(self::$hasSubtypes[$this->type]) && isset(self::$hasSubtypes[$type->type])) { 436 | if (count($this->subTypes) !== count($type->subTypes)) { 437 | return false; 438 | } 439 | // compare 440 | $other = $type->subTypes; 441 | foreach ($this->subTypes as $st1) { 442 | foreach ($other as $key => $st2) { 443 | if ($st1->equals($st2)) { 444 | unset($other[$key]); 445 | continue 2; 446 | } 447 | // We have a subtype that's not equal 448 | return false; 449 | } 450 | } 451 | return empty($other); 452 | } 453 | return true; 454 | } 455 | 456 | /** 457 | * @param Type $toRemove 458 | * 459 | * @return Type the removed type 460 | */ 461 | public function removeType(Type $type) { 462 | if (!isset(self::$hasSubtypes[$this->type])) { 463 | if ($this->equals($type)) { 464 | // left with an unknown type 465 | return Type::null(); 466 | } 467 | return $this; 468 | } 469 | $new = []; 470 | foreach ($this->subTypes as $key => $st) { 471 | if (!$st->equals($type)) { 472 | $new[] = $st; 473 | } 474 | } 475 | if (empty($new)) { 476 | throw new \LogicException('Unknown type encountered'); 477 | } elseif (count($new) === 1) { 478 | return $new[0]; 479 | } 480 | return new Type($this->type, $new); 481 | } 482 | 483 | } -------------------------------------------------------------------------------- /lib/PHPTypes/TypeInferer.php: -------------------------------------------------------------------------------- 1 | components = $components; 18 | $resolved = new \SplObjectStorage; 19 | $unresolved = new \SplObjectStorage; 20 | foreach ($components['variables'] as $op) { 21 | if (!empty($op->type) && $op->type->type !== Type::TYPE_UNKNOWN && $op->type->type !== Type::TYPE_MIXED) { 22 | $resolved[$op] = $op->type; 23 | } elseif ($op instanceof Operand\Literal) { 24 | $resolved[$op] = Type::fromValue($op->value); 25 | } else { 26 | $unresolved[$op] = Type::getAllPosibilities(); 27 | } 28 | } 29 | if (count($unresolved) === 0) { 30 | // short-circuit 31 | return; 32 | } 33 | 34 | do { 35 | echo "Round " . $round++ . " (" . count($unresolved) . " unresolved variables out of " . count($components['variables']) . ")\n"; 36 | $start = round(count($resolved) / count($unresolved), 6); 37 | $i = 0; 38 | $toRemove = []; 39 | foreach ($unresolved as $k => $var) { 40 | $i++; 41 | if ($i % 10 === 0) { 42 | echo "."; 43 | } 44 | if ($i % 800 === 0) { 45 | echo "\n"; 46 | } 47 | $type = $this->resolveVar($var, $resolved); 48 | if ($type) { 49 | $toRemove[] = $var; 50 | $resolved[$var] = $type; 51 | } 52 | } 53 | foreach ($toRemove as $remove) { 54 | $unresolved->detach($remove); 55 | } 56 | echo "\n"; 57 | } while (count($unresolved) > 0 && $start < round(count($resolved) / count($unresolved), 6)); 58 | foreach ($resolved as $var) { 59 | $var->type = $resolved[$var]; 60 | } 61 | foreach ($unresolved as $var) { 62 | $var->type = new Type(Type::TYPE_UNKNOWN); 63 | } 64 | } 65 | 66 | protected function resloveVar(Op $var, \SplObjectStorage $resolved) { 67 | var_dump($var); 68 | } 69 | } -------------------------------------------------------------------------------- /lib/PHPTypes/TypeReconstructor.php: -------------------------------------------------------------------------------- 1 | state = $state; 23 | // First resolve properties 24 | $this->resolveAllProperties(); 25 | $resolved = new SplObjectStorage; 26 | $unresolved = new SplObjectStorage; 27 | foreach ($state->variables as $op) { 28 | if (!empty($op->type) && $op->type->type !== Type::TYPE_UNKNOWN) { 29 | $resolved[$op] = $op->type; 30 | } elseif ($op instanceof Operand\BoundVariable && $op->scope === Operand\BoundVariable::SCOPE_OBJECT) { 31 | $resolved[$op] = $op->type = Type::fromDecl($op->extra->value); 32 | } elseif ($op instanceof Operand\Literal) { 33 | $resolved[$op] = $op->type = Type::fromValue($op->value); 34 | } else { 35 | $unresolved[$op] = Type::unknown(); 36 | } 37 | } 38 | 39 | if (count($unresolved) === 0) { 40 | // short-circuit 41 | return; 42 | } 43 | 44 | $round = 1; 45 | do { 46 | $start = count($resolved); 47 | $toRemove = []; 48 | foreach ($unresolved as $k => $var) { 49 | $type = $this->resolveVar($var, $resolved); 50 | if ($type) { 51 | $toRemove[] = $var; 52 | $resolved[$var] = $type; 53 | } 54 | } 55 | foreach ($toRemove as $remove) { 56 | $unresolved->detach($remove); 57 | } 58 | } while (count($unresolved) > 0 && $start < count($resolved)); 59 | foreach ($resolved as $var) { 60 | $var->type = $resolved[$var]; 61 | } 62 | foreach ($unresolved as $var) { 63 | $var->type = $unresolved[$var]; 64 | } 65 | } 66 | 67 | /** 68 | * @param Type[] $types 69 | * 70 | * @return Type 71 | */ 72 | protected function computeMergedType(array $types) { 73 | if (count($types) === 1) { 74 | return $types[0]; 75 | } 76 | $same = null; 77 | foreach ($types as $key => $type) { 78 | if (!$type instanceof Type) { 79 | var_dump($types); 80 | throw new \RuntimeException("Invalid type found"); 81 | } 82 | if (is_null($same)) { 83 | $same = $type; 84 | } elseif ($same && !$same->equals($type)) { 85 | $same = false; 86 | } 87 | if ($type->type === Type::TYPE_UNKNOWN) { 88 | return false; 89 | } 90 | } 91 | if ($same) { 92 | return $same; 93 | } 94 | return (new Type(Type::TYPE_UNION, $types))->simplify(); 95 | } 96 | 97 | protected function resolveVar(Operand $var, SplObjectStorage $resolved) { 98 | $types = []; 99 | foreach ($var->ops as $prev) { 100 | $type = $this->resolveVarOp($var, $prev, $resolved); 101 | if ($type) { 102 | if (!is_array($type)) { 103 | throw new \LogicException("Handler for " . get_class($prev) . " returned a non-array"); 104 | } 105 | foreach ($type as $t) { 106 | assert($t instanceof Type); 107 | $types[] = $t; 108 | } 109 | } else { 110 | return false; 111 | } 112 | } 113 | if (empty($types)) { 114 | return false; 115 | } 116 | return $this->computeMergedType($types); 117 | } 118 | 119 | protected function resolveVarOp(Operand $var, Op $op, SplObjectStorage $resolved) { 120 | $method = 'resolveOp_' . $op->getType(); 121 | if (method_exists($this, $method)) { 122 | return call_user_func([$this, $method], $var, $op, $resolved); 123 | } 124 | switch ($op->getType()) { 125 | case 'Expr_InstanceOf': 126 | case 'Expr_BinaryOp_Equal': 127 | case 'Expr_BinaryOp_NotEqual': 128 | case 'Expr_BinaryOp_Greater': 129 | case 'Expr_BinaryOp_GreaterOrEqual': 130 | case 'Expr_BinaryOp_Identical': 131 | case 'Expr_BinaryOp_NotIdentical': 132 | case 'Expr_BinaryOp_Smaller': 133 | case 'Expr_BinaryOp_SmallerOrEqual': 134 | case 'Expr_BinaryOp_LogicalAnd': 135 | case 'Expr_BinaryOp_LogicalOr': 136 | case 'Expr_BinaryOp_LogicalXor': 137 | case 'Expr_BooleanNot': 138 | case 'Expr_Cast_Bool': 139 | case 'Expr_Empty': 140 | case 'Expr_Isset': 141 | return [Type::bool()]; 142 | case 'Expr_BinaryOp_BitwiseAnd': 143 | case 'Expr_BinaryOp_BitwiseOr': 144 | case 'Expr_BinaryOp_BitwiseXor': 145 | if ($resolved->contains($op->left) && $resolved->contains($op->right)) { 146 | switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { 147 | case [Type::TYPE_STRING, Type::TYPE_STRING]: 148 | return [Type::string()]; 149 | default: 150 | return [Type::int()]; 151 | } 152 | } 153 | return false; 154 | case 'Expr_BitwiseNot': 155 | if ($resolved->contains($op->expr)) { 156 | switch ($resolved[$op->expr]->type) { 157 | case Type::TYPE_STRING: 158 | return [Type::string()]; 159 | default: 160 | return [Type::int()]; 161 | } 162 | } 163 | return false; 164 | case 'Expr_BinaryOp_Div': 165 | case 'Expr_BinaryOp_Plus': 166 | case 'Expr_BinaryOp_Minus': 167 | case 'Expr_BinaryOp_Mul': 168 | if ($resolved->contains($op->left) && $resolved->contains($op->right)) { 169 | switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { 170 | case [Type::TYPE_LONG, Type::TYPE_LONG]: 171 | return [Type::int()]; 172 | case [Type::TYPE_DOUBLE, TYPE::TYPE_LONG]: 173 | case [Type::TYPE_LONG, TYPE::TYPE_DOUBLE]: 174 | case [Type::TYPE_DOUBLE, TYPE::TYPE_DOUBLE]: 175 | return [Type::float()]; 176 | case [Type::TYPE_ARRAY, Type::TYPE_ARRAY]: 177 | $sub = $this->computeMergedType(array_merge($resolved[$op->left]->subTypes, $resolved[$op->right]->subTypes)); 178 | if ($sub) { 179 | return [new Type(Type::TYPE_ARRAY, [$sub])]; 180 | } 181 | return [new Type(Type::TYPE_ARRAY)]; 182 | default: 183 | return [Type::mixed()]; 184 | throw new \RuntimeException("Math op on unknown types {$resolved[$op->left]} + {$resolved[$op->right]}"); 185 | } 186 | } 187 | return false; 188 | case 'Expr_BinaryOp_Concat': 189 | case 'Expr_Cast_String': 190 | case 'Expr_ConcatList': 191 | return [Type::string()]; 192 | case 'Expr_BinaryOp_Mod': 193 | case 'Expr_BinaryOp_ShiftLeft': 194 | case 'Expr_BinaryOp_ShiftRight': 195 | case 'Expr_Cast_Int': 196 | case 'Expr_Print': 197 | return [Type::int()]; 198 | case 'Expr_Cast_Double': 199 | return [Type::float()]; 200 | case 'Expr_UnaryMinus': 201 | case 'Expr_UnaryPlus': 202 | if ($resolved->contains($op->expr)) { 203 | switch ($resolved[$op->expr]->type) { 204 | case Type::TYPE_LONG: 205 | case Type::TYPE_DOUBLE: 206 | return [$resolved[$op->expr]]; 207 | } 208 | return [Type::numeric()]; 209 | } 210 | return false; 211 | case 'Expr_Eval': 212 | return false; 213 | case 'Iterator_Key': 214 | if ($resolved->contains($op->var)) { 215 | // TODO: implement this as well 216 | return false; 217 | } 218 | return false; 219 | case 'Expr_Exit': 220 | case 'Iterator_Reset': 221 | return [Type::null()]; 222 | case 'Iterator_Valid': 223 | return [Type::bool()]; 224 | case 'Iterator_Value': 225 | if ($resolved->contains($op->var)) { 226 | if ($resolved[$op->var]->subTypes) { 227 | return $resolved[$op->var]->subTypes; 228 | } 229 | return false; 230 | } 231 | return false; 232 | case 'Expr_StaticCall': 233 | return $this->resolveMethodCall($op->class, $op->name, $op, $resolved); 234 | case 'Expr_MethodCall': 235 | return $this->resolveMethodCall($op->var, $op->name, $op, $resolved); 236 | case 'Expr_Yield': 237 | case 'Expr_Include': 238 | // TODO: we may be able to determine these... 239 | return false; 240 | } 241 | throw new \LogicException("Unknown variable op found: " . $op->getType()); 242 | } 243 | 244 | protected function resolveOp_Expr_Array(Operand $var, Op\Expr\Array_ $op, SplObjectStorage $resolved) { 245 | $types = []; 246 | foreach ($op->values as $value) { 247 | if (!isset($resolved[$value])) { 248 | return false; 249 | } 250 | $types[] = $resolved[$value]; 251 | } 252 | if (empty($types)) { 253 | return [new Type(Type::TYPE_ARRAY)]; 254 | } 255 | $r = $this->computeMergedType($types); 256 | if ($r) { 257 | return [new Type(Type::TYPE_ARRAY, [$r])]; 258 | } 259 | return [new Type(Type::TYPE_ARRAY)]; 260 | } 261 | 262 | protected function resolveOp_Expr_Cast_Array(Operand $var, Op\Expr\Cast\Array_ $op, SplObjectStorage $resolved) { 263 | // Todo: determine subtypes better 264 | return [new Type(Type::TYPE_ARRAY)]; 265 | } 266 | 267 | protected function resolveOp_Expr_ArrayDimFetch(Operand $var, Op\Expr\ArrayDimFetch $op, SplObjectStorage $resolved) { 268 | if ($resolved->contains($op->var)) { 269 | // Todo: determine subtypes better 270 | $type = $resolved[$op->var]; 271 | if ($type->subTypes) { 272 | return $type->subTypes; 273 | } 274 | if ($type->type === Type::TYPE_STRING) { 275 | return [$type]; 276 | } 277 | return [Type::mixed()]; 278 | } 279 | return false; 280 | } 281 | 282 | protected function resolveOp_Expr_Assign(Operand $var, Op\Expr\Assign $op, SplObjectStorage $resolved) { 283 | if ($resolved->contains($op->expr)) { 284 | return [$resolved[$op->expr]]; 285 | } 286 | return false; 287 | } 288 | 289 | protected function resolveOp_Expr_AssignRef(Operand $var, Op\Expr\AssignRef $op, SplObjectStorage $resolved) { 290 | if ($resolved->contains($op->expr)) { 291 | return [$resolved[$op->expr]]; 292 | } 293 | return false; 294 | } 295 | 296 | protected function resolveOp_Expr_Cast_Object(Operand $var, Op\Expr\Cast\Object_ $op, SplObjectStorage $resolved) { 297 | if ($resolved->contains($op->expr)) { 298 | if ($resolved[$op->expr]->type->resolves(Type::object())) { 299 | return [$resolved[$op->expr]]; 300 | } 301 | return [new Type(Type::TYPE_OBJECT, [], 'stdClass')]; 302 | } 303 | return false; 304 | } 305 | 306 | protected function resolveOp_Expr_Clone(Operand $var, Op\Expr\Clone_ $op, SplObjectStorage $resolved) { 307 | if ($resolved->contains($op->expr)) { 308 | return [$resolved[$op->expr]]; 309 | } 310 | return false; 311 | } 312 | 313 | protected function resolveOp_Expr_Closure(Operand $var, Op\Expr\Closure $op, SplObjectStorage $resolved) { 314 | return [new Type(Type::TYPE_OBJECT, [], "Closure")]; 315 | } 316 | 317 | protected function resolveOp_Expr_FuncCall(Operand $var, Op\Expr\FuncCall $op, SplObjectStorage $resolved) { 318 | if ($op->name instanceof Operand\Literal) { 319 | $name = strtolower($op->name->value); 320 | if (isset($this->state->functionLookup[$name])) { 321 | $result = []; 322 | foreach ($this->state->functionLookup[$name] as $func) { 323 | if ($func->returnType) { 324 | $result[] = Type::fromDecl($func->returnType->value); 325 | } else { 326 | // Check doc comment 327 | $result[] = Type::extractTypeFromComment("return", $func->getAttribute('doccomment')); 328 | } 329 | } 330 | return $result; 331 | } else { 332 | if (isset($this->state->internalTypeInfo->functions[$name])) { 333 | $type = $this->state->internalTypeInfo->functions[$name]; 334 | if (empty($type['return'])) { 335 | return false; 336 | } 337 | return [Type::fromDecl($type['return'])]; 338 | } 339 | } 340 | } 341 | // we can't resolve the function 342 | return false; 343 | } 344 | 345 | protected function resolveOp_Expr_New(Operand $var, Op\Expr\New_ $op, SplObjectStorage $resolved) { 346 | $type = $this->getClassType($op->class, $resolved); 347 | if ($type) { 348 | return [$type]; 349 | } 350 | return [Type::object()]; 351 | } 352 | 353 | protected function resolveOp_Expr_Param(Operand $var, Op\Expr\Param $op, SplObjectStorage $resolved) { 354 | // $docType = Type::extractTypeFromComment("param", $op->function->getAttribute('doccomment'), $op->name->value); 355 | if ($op->type) { 356 | $type = Type::fromDecl($op->type->value); 357 | if ($op->defaultVar) { 358 | if ($op->defaultBlock->children[0]->getType() === "Expr_ConstFetch" && strtolower($op->defaultBlock->children[0]->name->value) === "null") { 359 | $type = (new Type(Type::TYPE_UNION, [$type, Type::null()]))->simplify(); 360 | } 361 | } 362 | // if ($docType !== Type::mixed() && $this->state->resolver->resolves($docType, $type)) { 363 | // // return the more specific 364 | // return [$docType]; 365 | // } 366 | return [$type]; 367 | } 368 | return [Type::mixed()]; 369 | //return [$docType]; 370 | } 371 | 372 | protected function resolveOp_Expr_StaticPropertyFetch(Operand $var, Op $op, SplObjectStorage $resolved) { 373 | return $this->resolveOp_Expr_PropertyFetch($var, $op, $resolved); 374 | } 375 | 376 | protected function resolveOp_Expr_PropertyFetch(Operand $var, Op $op, SplObjectStorage $resolved) { 377 | if (!$op->name instanceof Operand\Literal) { 378 | // variable property fetch 379 | return [Type::mixed()]; 380 | } 381 | $propName = $op->name->value; 382 | if ($op instanceof Op\Expr\StaticPropertyFetch) { 383 | $objType = $this->getClassType($op->class, $resolved); 384 | } else { 385 | $objType = $this->getClassType($op->var, $resolved); 386 | } 387 | if ($objType) { 388 | return $this->resolveProperty($objType, $propName); 389 | } 390 | return false; 391 | } 392 | 393 | protected function resolveOp_Expr_Assertion(Operand $var, Op $op, SplObjectStorage $resolved) { 394 | $tmp = $this->processAssertion($op->assertion, $op->expr, $resolved); 395 | if ($tmp) { 396 | return [$tmp]; 397 | } 398 | return false; 399 | } 400 | 401 | protected function resolveOp_Expr_ConstFetch(Operand $var, Op\Expr\ConstFetch $op, SplObjectStorage $resolved) { 402 | if ($op->name instanceof Operand\Literal) { 403 | $constant = strtolower($op->name->value); 404 | switch ($constant) { 405 | case 'true': 406 | case 'false': 407 | return [Type::bool()]; 408 | case 'null': 409 | return [Type::null()]; 410 | default: 411 | if (isset($this->state->constants[$op->name->value])) { 412 | $return = []; 413 | foreach ($this->state->constants[$op->name->value] as $value) { 414 | if (!$resolved->contains($value->value)) { 415 | return false; 416 | } 417 | $return[] = $resolved[$value->value]; 418 | } 419 | return $return; 420 | } 421 | } 422 | } 423 | return false; 424 | } 425 | 426 | protected function resolveOp_Expr_ClassConstFetch(Operand $var, Op\Expr\ClassConstFetch $op, SplObjectStorage $resolved) { 427 | $classes = []; 428 | if ($op->class instanceof Operand\Literal) { 429 | $class = strtolower($op->class->value); 430 | return $this->resolveClassConstant($class, $op, $resolved); 431 | } elseif ($resolved->contains($op->class)) { 432 | $type = $resolved[$op->class]; 433 | if ($type->type !== Type::TYPE_OBJECT || empty($type->userType)) { 434 | // give up 435 | return false; 436 | } 437 | return $this->resolveClassConstant(strtolower($type->userType), $op, $resolved); 438 | } 439 | return false; 440 | } 441 | 442 | protected function resolveOp_Phi(Operand $var, Op\Phi $op, SplObjectStorage $resolved) { 443 | $types = []; 444 | $resolveFully = true; 445 | foreach ($op->vars as $v) { 446 | if ($resolved->contains($v)) { 447 | $types[] = $resolved[$v]; 448 | } else { 449 | $resolveFully = false; 450 | } 451 | } 452 | if (empty($types)) { 453 | return false; 454 | } 455 | $type = $this->computeMergedType($types); 456 | if ($type) { 457 | if ($resolveFully) { 458 | return [$type]; 459 | } 460 | // leave on unresolved list to try again next round 461 | $resolved[$var] = $type; 462 | } 463 | return false; 464 | } 465 | 466 | protected function findMethod($class, $name) { 467 | foreach ($class->stmts->children as $stmt) { 468 | if ($stmt instanceof Op\Stmt\ClassMethod) { 469 | if (strtolower($stmt->func->name) === $name) { 470 | return $stmt; 471 | } 472 | } 473 | } 474 | if ($name !== '__call') { 475 | return $this->findMethod($class, '__call'); 476 | } 477 | return null; 478 | } 479 | 480 | protected function findProperty($class, $name) { 481 | foreach ($class->stmts->children as $stmt) { 482 | if ($stmt instanceof Op\Stmt\Property) { 483 | if ($stmt->name->value === $name) { 484 | return $stmt; 485 | } 486 | } 487 | } 488 | return null; 489 | } 490 | 491 | protected function resolveAllProperties() { 492 | foreach ($this->state->classes as $class) { 493 | foreach ($class->stmts->children as $stmt) { 494 | if ($stmt instanceof Op\Stmt\Property) { 495 | $stmt->type = Type::extractTypeFromComment("var", $stmt->getAttribute('doccomment')); 496 | } 497 | } 498 | } 499 | } 500 | 501 | protected function resolveClassConstant($class, $op, SplObjectStorage $resolved) { 502 | $try = $class . '::' . $op->name->value; 503 | if (isset($this->state->constants[$try])) { 504 | $types = []; 505 | foreach ($this->state->constants[$try] as $const) { 506 | if ($resolved->contains($const->value)) { 507 | $types[] = $resolved[$const->value]; 508 | } else { 509 | // Not every 510 | return false; 511 | } 512 | } 513 | return $types; 514 | } 515 | if (!isset($this->state->classResolvedBy[$class])) { 516 | // can't find classes 517 | return false; 518 | } 519 | $types = []; 520 | foreach ($this->state->classResolves[$class] as $name => $_) { 521 | $try = $name . '::' . $op->name->value; 522 | if (isset($this->state->constants[$try])) { 523 | foreach ($this->state->constants[$try] as $const) { 524 | if ($resolved->contains($const->value)) { 525 | $types[] = $resolved[$const->value]; 526 | } else { 527 | // Not every is resolved yet 528 | return false; 529 | } 530 | } 531 | } 532 | } 533 | if (empty($types)) { 534 | return false; 535 | } 536 | return $types; 537 | } 538 | 539 | /** 540 | * @param Type $objType 541 | * @param string $propName 542 | * 543 | * @return Type[]|false 544 | */ 545 | private function resolveProperty(Type $objType, $propName) { 546 | if ($objType->type === Type::TYPE_OBJECT) { 547 | $types = []; 548 | $ut = strtolower($objType->userType); 549 | if (!isset($this->state->classResolves[$ut])) { 550 | // unknown type 551 | return false; 552 | } 553 | foreach ($this->state->classResolves[$ut] as $class) { 554 | // Lookup property on class 555 | $property = $this->findProperty($class, $propName); 556 | if ($property) { 557 | if ($property->type) { 558 | $types[] = $property->type; 559 | } else { 560 | echo "Property found to be untyped: $propName\n"; 561 | // untyped property 562 | return false; 563 | } 564 | } 565 | } 566 | if ($types) { 567 | return $types; 568 | } 569 | } 570 | return false; 571 | } 572 | 573 | private function resolveMethodCall($class, $name, Op $op, SplObjectStorage $resolved) { 574 | if (!$name instanceof Operand\Literal) { 575 | // Variable Method Call 576 | return false; 577 | } 578 | $name = strtolower($name->value); 579 | if ($resolved->contains($class)) { 580 | $userType = ''; 581 | if ($resolved[$class]->type === Type::TYPE_STRING) { 582 | if (!$class instanceof Operand\Literal) { 583 | // variable class name, for now just return object 584 | return [Type::mixed()]; 585 | } 586 | $userType = $class->value; 587 | } elseif ($resolved[$class]->type !== Type::TYPE_OBJECT) { 588 | return false; 589 | } else { 590 | $userType = $resolved[$class]->userType; 591 | } 592 | $types = []; 593 | $className = strtolower($userType); 594 | if (!isset($this->state->classResolves[$className])) { 595 | if (isset($this->state->internalTypeInfo->methods[$className])) { 596 | $types = []; 597 | foreach ($this->state->internalTypeInfo->methods[$className]['extends'] as $child) { 598 | if (isset($this->state->internalTypeInfo->methods[$child]['methods'][$name])) { 599 | $method = $this->state->internalTypeInfo->methods[$child]['methods'][$name]; 600 | if ($method['return']) { 601 | $types[] = Type::fromDecl($method['return']); 602 | } 603 | } 604 | } 605 | if (!empty($types)) { 606 | return $types; 607 | } 608 | } 609 | return false; 610 | } 611 | foreach ($this->state->classResolves[$className] as $class) { 612 | $method = $this->findMethod($class, $name); 613 | if (!$method) { 614 | continue; 615 | } 616 | $doc = Type::extractTypeFromComment("return", $method->getAttribute('doccomment')); 617 | 618 | if (!isset($method->func->returnType)) { 619 | $types[] = $doc; 620 | } else { 621 | $decl = Type::fromDecl($method->func->returnType->value); 622 | if ($this->state->resolver->resolves($doc, $decl)) { 623 | // doc is a subset 624 | $types[] = $doc; 625 | } else { 626 | $types[] = $decl; 627 | } 628 | } 629 | } 630 | if (!empty($types)) { 631 | return $types; 632 | } 633 | return false; 634 | } 635 | return false; 636 | } 637 | 638 | protected function getClassType(Operand $var, SplObjectStorage $resolved) { 639 | if ($var instanceof Operand\Literal) { 640 | return new Type(Type::TYPE_OBJECT, [], $var->value); 641 | } elseif ($var instanceof Operand\BoundVariable && $var->scope === Operand\BoundVariable::SCOPE_OBJECT) { 642 | assert($var->extra instanceof Operand\Literal); 643 | return Type::fromDecl($var->extra->value); 644 | } elseif ($resolved->contains($var)) { 645 | $type = $resolved[$var]; 646 | if ($type->type === Type::TYPE_OBJECT) { 647 | return $type; 648 | } 649 | } 650 | // We don't know the type 651 | return false; 652 | } 653 | 654 | protected function processAssertion(Assertion $assertion, Operand $source, SplObjectStorage $resolved) { 655 | if ($assertion instanceof Assertion\TypeAssertion) { 656 | $tmp = $this->processTypeAssertion($assertion, $source, $resolved); 657 | if ($tmp) { 658 | return $tmp; 659 | } 660 | } elseif ($assertion instanceof Assertion\NegatedAssertion) { 661 | $op = $this->processAssertion($assertion->value[0], $source, $resolved); 662 | if ($op instanceof Type) { 663 | // negated type assertion 664 | if (isset($resolved[$source])) { 665 | return $resolved[$source]->removeType($op); 666 | } 667 | // Todo, figure out how to wait for resolving 668 | return Type::mixed()->removeType($op); 669 | } 670 | } 671 | return false; 672 | } 673 | 674 | protected function processTypeAssertion(Assertion\TypeAssertion $assertion, Operand $source, SplObjectStorage $resolved) { 675 | if ($assertion->value instanceof Operand) { 676 | if ($assertion->value instanceof Operand\Literal) { 677 | return Type::fromDecl($assertion->value->value); 678 | } 679 | if (isset($resolved[$assertion->value])) { 680 | return $resolved[$assertion->value]; 681 | } 682 | return false; 683 | } 684 | $subTypes = []; 685 | foreach ($assertion->value as $sub) { 686 | $subTypes[] = $subType = $this->processTypeAssertion($sub, $source, $resolved); 687 | if (!$subType) { 688 | // Not fully resolved yet 689 | return false; 690 | } 691 | } 692 | $type = $assertion->mode === Assertion::MODE_UNION ? Type::TYPE_UNION : Type::TYPE_INTERSECTION; 693 | return new Type($type, $subTypes); 694 | } 695 | } -------------------------------------------------------------------------------- /lib/PHPTypes/TypeResolver.php: -------------------------------------------------------------------------------- 1 | state = $state; 20 | $this->callableUnion = Type::fromDecl("string|array|object"); 21 | } 22 | 23 | public function resolves(Type $a, Type $b) { 24 | if ($a->equals($b)) { 25 | return true; 26 | } 27 | if ($b->type === Type::TYPE_CALLABLE) { 28 | return $this->resolves($a, $this->callableUnion); 29 | } 30 | if ($a->type === Type::TYPE_OBJECT && $b->type === Type::TYPE_OBJECT) { 31 | return $this->checkUserTypes($a->userType, $b->userType); 32 | } 33 | if ($a->type === Type::TYPE_LONG && $b->type === Type::TYPE_DOUBLE) { 34 | return true; 35 | } 36 | if ($a->type === Type::TYPE_ARRAY && $b->type === Type::TYPE_ARRAY) { 37 | if (!$b->subTypes) { 38 | return true; 39 | } 40 | if (!$a->subTypes) { 41 | // We need a specific array 42 | return false; 43 | } 44 | return ($this->resolves($a->subTypes[0], $b->subTypes[0])); 45 | } 46 | if ($a->type === Type::TYPE_UNION) { 47 | foreach ($a->subTypes as $st) { 48 | if ($this->resolves($st, $b)) { 49 | // All must resolve 50 | return false; 51 | } 52 | } 53 | return true; 54 | } 55 | if ($a->type === Type::TYPE_INTERSECTION) { 56 | foreach ($a->subTypes as $st) { 57 | if ($this->resolves($st, $b)) { 58 | // At least one resolves it 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | if ($b->type === Type::TYPE_UNION) { 65 | foreach ($b->subTypes as $st) { 66 | if ($this->resolves($a, $st)) { 67 | // At least one resolves it 68 | return true; 69 | } 70 | } 71 | return false; 72 | } 73 | if ($b->type === Type::TYPE_INTERSECTION) { 74 | foreach ($b->subTypes as $st) { 75 | if (!$this->resolves($a, $st)) { 76 | // At least one resolves it 77 | return false; 78 | } 79 | } 80 | return true; 81 | } 82 | return false; 83 | } 84 | 85 | private function checkUserTypes($a, $b) { 86 | $a = strtolower($a); 87 | $b = strtolower($b); 88 | if (isset($this->state->classResolves[$b][$a])) { 89 | return true; 90 | } 91 | // TODO: take care of internal types 92 | return false; 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./test/ 16 | 17 | 18 | 19 | 20 | 21 | ./lib/PHPTypes/ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/PHPTypes/ReconstructorTest.php: -------------------------------------------------------------------------------- 1 | addVisitor(new PhpParser\NodeVisitor\NameResolver); 26 | $parser = new Parser((new ParserFactory)->create(ParserFactory::PREFER_PHP7), $astTraverser); 27 | $script = $parser->parse($code, 'foo.php'); 28 | 29 | $traverser = new Traverser(); 30 | $traverser->addVisitor(new Visitor\Simplifier()); 31 | $traverser->traverse($script); 32 | 33 | $reconstructor = new TypeReconstructor; 34 | $state = new State($script); 35 | $reconstructor->resolve($state); 36 | 37 | $printer = new Printer\Text(); 38 | $this->assertEquals( 39 | $this->canonicalize($expectedDump), 40 | $this->canonicalize($printer->printScript($script)) 41 | ); 42 | } 43 | 44 | public function provideTestParseAndDump() { 45 | $dir = __DIR__ . '/../code'; 46 | 47 | $iter = new \RecursiveIteratorIterator( 48 | new \RecursiveDirectoryIterator($dir), 49 | \RecursiveIteratorIterator::LEAVES_ONLY 50 | ); 51 | 52 | foreach ($iter as $file) { 53 | if (!$file->isFile()) { 54 | continue; 55 | } 56 | 57 | $contents = file_get_contents($file); 58 | yield array_merge([$file->getBasename()], explode('-----', $contents)); 59 | } 60 | } 61 | 62 | protected function canonicalize($str) { 63 | // trim from both sides 64 | $str = trim($str); 65 | 66 | // normalize EOL to \n 67 | $str = str_replace(["\r\n", "\r"], "\n", $str); 68 | 69 | // trim right side of all lines 70 | return implode("\n", array_map('rtrim', explode("\n", $str))); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/PHPTypes/TypeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($result, $type); 32 | $this->assertEquals($decl, (string) $type); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /test/code/basic.test: -------------------------------------------------------------------------------- 1 | #1<$x> 9 | expr: LITERAL(1) 10 | result: Var#2 11 | Expr_Assign 12 | var: Var#3<$y> 13 | expr: LITERAL(2) 14 | result: Var#4 15 | Terminal_Return -------------------------------------------------------------------------------- /test/code/bitwiseNot.test: -------------------------------------------------------------------------------- 1 | (1) 9 | result: Var#1 10 | Expr_BitwiseNot 11 | expr: LITERAL('') 12 | result: Var#2 13 | Terminal_Return -------------------------------------------------------------------------------- /test/code/class.test: -------------------------------------------------------------------------------- 1 | bar($abc); 6 | } 7 | 8 | protected function bar(float $def): int { 9 | return floor($def); 10 | } 11 | } 12 | ----- 13 | Block#1 14 | Stmt_Class 15 | name: LITERAL('A') 16 | stmts: Block#2 17 | Terminal_Return 18 | 19 | Block#2 20 | Stmt_ClassMethod 21 | Stmt_ClassMethod 22 | 23 | Function A::foo(): 24 | Block#1 25 | Expr_Param 26 | name: LITERAL('abc') 27 | result: Var#1<$abc> 28 | Expr_MethodCall 29 | var: this<$this> 30 | name: LITERAL('bar') 31 | args[0]: Var#1<$abc> 32 | result: Var#2 33 | Terminal_Return 34 | expr: Var#2 35 | 36 | Function A::bar(): 37 | Block#1 38 | Expr_Param 39 | name: LITERAL('def') 40 | result: Var#1<$def> 41 | Expr_FuncCall 42 | name: LITERAL('floor') 43 | args[0]: Var#1<$def> 44 | result: Var#2 45 | Terminal_Return 46 | expr: Var#2 --------------------------------------------------------------------------------