|   1:  | <?php declare(strict_types = 1); | 
|   2:  |  | 
|   3:  | namespace PHPStan\Testing; | 
|   4:  |  | 
|   5:  | use PhpParser\Node; | 
|   6:  | use PhpParser\Node\Expr\StaticCall; | 
|   7:  | use PhpParser\Node\Name; | 
|   8:  | use PHPStan\Analyser\NodeScopeResolver; | 
|   9:  | use PHPStan\Analyser\Scope; | 
|  10:  | use PHPStan\Analyser\ScopeContext; | 
|  11:  | use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; | 
|  12:  | use PHPStan\File\FileHelper; | 
|  13:  | use PHPStan\Php\PhpVersion; | 
|  14:  | use PHPStan\PhpDoc\PhpDocInheritanceResolver; | 
|  15:  | use PHPStan\PhpDoc\StubPhpDocProvider; | 
|  16:  | use PHPStan\Reflection\InitializerExprTypeResolver; | 
|  17:  | use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; | 
|  18:  | use PHPStan\TrinaryLogic; | 
|  19:  | use PHPStan\Type\FileTypeMapper; | 
|  20:  | use PHPStan\Type\VerbosityLevel; | 
|  21:  | use function array_map; | 
|  22:  | use function array_merge; | 
|  23:  | use function count; | 
|  24:  | use function is_string; | 
|  25:  | use function sprintf; | 
|  26:  |  | 
|  27:  |  | 
|  28:  | abstract class TypeInferenceTestCase extends PHPStanTestCase | 
|  29:  | { | 
|  30:  |  | 
|  31:  | 	 | 
|  32:  |  | 
|  33:  |  | 
|  34:  |  | 
|  35:  | 	public function processFile( | 
|  36:  | 		string $file, | 
|  37:  | 		callable $callback, | 
|  38:  | 		array $dynamicConstantNames = [], | 
|  39:  | 	): void | 
|  40:  | 	{ | 
|  41:  | 		$reflectionProvider = $this->createReflectionProvider(); | 
|  42:  | 		$typeSpecifier = self::getContainer()->getService('typeSpecifier'); | 
|  43:  | 		$fileHelper = self::getContainer()->getByType(FileHelper::class); | 
|  44:  | 		$resolver = new NodeScopeResolver( | 
|  45:  | 			$reflectionProvider, | 
|  46:  | 			self::getContainer()->getByType(InitializerExprTypeResolver::class), | 
|  47:  | 			self::getReflector(), | 
|  48:  | 			$this->getClassReflectionExtensionRegistryProvider(), | 
|  49:  | 			$this->getParser(), | 
|  50:  | 			self::getContainer()->getByType(FileTypeMapper::class), | 
|  51:  | 			self::getContainer()->getByType(StubPhpDocProvider::class), | 
|  52:  | 			self::getContainer()->getByType(PhpVersion::class), | 
|  53:  | 			self::getContainer()->getByType(PhpDocInheritanceResolver::class), | 
|  54:  | 			self::getContainer()->getByType(FileHelper::class), | 
|  55:  | 			$typeSpecifier, | 
|  56:  | 			self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), | 
|  57:  | 			self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class), | 
|  58:  | 			true, | 
|  59:  | 			true, | 
|  60:  | 			$this->getEarlyTerminatingMethodCalls(), | 
|  61:  | 			$this->getEarlyTerminatingFunctionCalls(), | 
|  62:  | 			true, | 
|  63:  | 		); | 
|  64:  | 		$resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], $this->getAdditionalAnalysedFiles()))); | 
|  65:  |  | 
|  66:  | 		$scopeFactory = $this->createScopeFactory($reflectionProvider, $typeSpecifier, $dynamicConstantNames); | 
|  67:  | 		$scope = $scopeFactory->create(ScopeContext::create($file)); | 
|  68:  |  | 
|  69:  | 		$resolver->processNodes( | 
|  70:  | 			$this->getParser()->parseFile($file), | 
|  71:  | 			$scope, | 
|  72:  | 			$callback, | 
|  73:  | 		); | 
|  74:  | 	} | 
|  75:  |  | 
|  76:  | 	 | 
|  77:  |  | 
|  78:  |  | 
|  79:  |  | 
|  80:  | 	public function assertFileAsserts( | 
|  81:  | 		string $assertType, | 
|  82:  | 		string $file, | 
|  83:  | 		...$args, | 
|  84:  | 	): void | 
|  85:  | 	{ | 
|  86:  | 		if ($assertType === 'type') { | 
|  87:  | 			$expectedType = $args[0]; | 
|  88:  | 			$expected = $expectedType->getValue(); | 
|  89:  | 			$actualType = $args[1]; | 
|  90:  | 			$actual = $actualType->describe(VerbosityLevel::precise()); | 
|  91:  | 			$this->assertSame( | 
|  92:  | 				$expected, | 
|  93:  | 				$actual, | 
|  94:  | 				sprintf('Expected type %s, got type %s in %s on line %d.', $expected, $actual, $file, $args[2]), | 
|  95:  | 			); | 
|  96:  | 		} elseif ($assertType === 'variableCertainty') { | 
|  97:  | 			$expectedCertainty = $args[0]; | 
|  98:  | 			$actualCertainty = $args[1]; | 
|  99:  | 			$variableName = $args[2]; | 
| 100:  | 			$this->assertTrue( | 
| 101:  | 				$expectedCertainty->equals($actualCertainty), | 
| 102:  | 				sprintf('Expected %s, actual certainty of variable $%s is %s', $expectedCertainty->describe(), $variableName, $actualCertainty->describe()), | 
| 103:  | 			); | 
| 104:  | 		} | 
| 105:  | 	} | 
| 106:  |  | 
| 107:  | 	 | 
| 108:  |  | 
| 109:  |  | 
| 110:  |  | 
| 111:  | 	public function gatherAssertTypes(string $file): array | 
| 112:  | 	{ | 
| 113:  | 		$asserts = []; | 
| 114:  | 		$this->processFile($file, function (Node $node, Scope $scope) use (&$asserts, $file): void { | 
| 115:  | 			if (!$node instanceof Node\Expr\FuncCall) { | 
| 116:  | 				return; | 
| 117:  | 			} | 
| 118:  |  | 
| 119:  | 			$nameNode = $node->name; | 
| 120:  | 			if (!$nameNode instanceof Name) { | 
| 121:  | 				return; | 
| 122:  | 			} | 
| 123:  |  | 
| 124:  | 			$functionName = $nameNode->toString(); | 
| 125:  | 			if ($functionName === 'PHPStan\\Testing\\assertType') { | 
| 126:  | 				$expectedType = $scope->getType($node->getArgs()[0]->value); | 
| 127:  | 				$actualType = $scope->getType($node->getArgs()[1]->value); | 
| 128:  | 				$assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; | 
| 129:  | 			} elseif ($functionName === 'PHPStan\\Testing\\assertNativeType') { | 
| 130:  | 				$nativeScope = $scope->doNotTreatPhpDocTypesAsCertain(); | 
| 131:  | 				$expectedType = $nativeScope->getNativeType($node->getArgs()[0]->value); | 
| 132:  | 				$actualType = $nativeScope->getNativeType($node->getArgs()[1]->value); | 
| 133:  | 				$assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; | 
| 134:  | 			} elseif ($functionName === 'PHPStan\\Testing\\assertVariableCertainty') { | 
| 135:  | 				$certainty = $node->getArgs()[0]->value; | 
| 136:  | 				if (!$certainty instanceof StaticCall) { | 
| 137:  | 					$this->fail(sprintf('First argument of %s() must be TrinaryLogic call', $functionName)); | 
| 138:  | 				} | 
| 139:  | 				if (!$certainty->class instanceof Node\Name) { | 
| 140:  | 					$this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); | 
| 141:  | 				} | 
| 142:  |  | 
| 143:  | 				if ($certainty->class->toString() !== 'PHPStan\\TrinaryLogic') { | 
| 144:  | 					$this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); | 
| 145:  | 				} | 
| 146:  |  | 
| 147:  | 				if (!$certainty->name instanceof Node\Identifier) { | 
| 148:  | 					$this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); | 
| 149:  | 				} | 
| 150:  |  | 
| 151:  | 				 | 
| 152:  | 				$expectedertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); | 
| 153:  | 				$variable = $node->getArgs()[1]->value; | 
| 154:  | 				if (!$variable instanceof Node\Expr\Variable) { | 
| 155:  | 					$this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); | 
| 156:  | 				} | 
| 157:  | 				if (!is_string($variable->name)) { | 
| 158:  | 					$this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); | 
| 159:  | 				} | 
| 160:  |  | 
| 161:  | 				$actualCertaintyValue = $scope->hasVariableType($variable->name); | 
| 162:  | 				$assert = ['variableCertainty', $file, $expectedertaintyValue, $actualCertaintyValue, $variable->name]; | 
| 163:  | 			} else { | 
| 164:  | 				return; | 
| 165:  | 			} | 
| 166:  |  | 
| 167:  | 			if (count($node->getArgs()) !== 2) { | 
| 168:  | 				$this->fail(sprintf( | 
| 169:  | 					'ERROR: Wrong %s() call on line %d.', | 
| 170:  | 					$functionName, | 
| 171:  | 					$node->getLine(), | 
| 172:  | 				)); | 
| 173:  | 			} | 
| 174:  |  | 
| 175:  | 			$asserts[$file . ':' . $node->getLine()] = $assert; | 
| 176:  | 		}); | 
| 177:  |  | 
| 178:  | 		return $asserts; | 
| 179:  | 	} | 
| 180:  |  | 
| 181:  | 	 | 
| 182:  | 	protected function getAdditionalAnalysedFiles(): array | 
| 183:  | 	{ | 
| 184:  | 		return []; | 
| 185:  | 	} | 
| 186:  |  | 
| 187:  | 	 | 
| 188:  | 	protected function getEarlyTerminatingMethodCalls(): array | 
| 189:  | 	{ | 
| 190:  | 		return []; | 
| 191:  | 	} | 
| 192:  |  | 
| 193:  | 	 | 
| 194:  | 	protected function getEarlyTerminatingFunctionCalls(): array | 
| 195:  | 	{ | 
| 196:  | 		return []; | 
| 197:  | 	} | 
| 198:  |  | 
| 199:  | } | 
| 200:  |  |