|
2 | 2 |
|
3 | 3 | namespace PHPStan\Testing;
|
4 | 4 |
|
| 5 | +use PhpParser\Node; |
| 6 | +use PhpParser\Node\Expr\StaticCall; |
| 7 | +use PhpParser\Node\Name; |
5 | 8 | use PHPStan\Analyser\DirectScopeFactory;
|
6 | 9 | use PHPStan\Analyser\NodeScopeResolver;
|
| 10 | +use PHPStan\Analyser\Scope; |
7 | 11 | use PHPStan\Analyser\ScopeContext;
|
8 | 12 | use PHPStan\Broker\AnonymousClassNameHelper;
|
9 | 13 | use PHPStan\Broker\Broker;
|
|
15 | 19 | use PHPStan\PhpDoc\PhpDocNodeResolver;
|
16 | 20 | use PHPStan\PhpDoc\PhpDocStringResolver;
|
17 | 21 | use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider;
|
| 22 | +use PHPStan\TrinaryLogic; |
18 | 23 | use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
19 | 24 | use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
|
20 | 25 | use PHPStan\Type\FileTypeMapper;
|
| 26 | +use PHPStan\Type\VerbosityLevel; |
21 | 27 |
|
22 | 28 | abstract class TypeInferenceTest extends \PHPStan\Testing\TestCase
|
23 | 29 | {
|
@@ -97,6 +103,111 @@ public function processFile(
|
97 | 103 | );
|
98 | 104 | }
|
99 | 105 |
|
| 106 | + /** |
| 107 | + * @param string $assertType |
| 108 | + * @param string $file |
| 109 | + * @param mixed ...$args |
| 110 | + */ |
| 111 | + public function assertFileAsserts( |
| 112 | + string $assertType, |
| 113 | + string $file, |
| 114 | + ...$args |
| 115 | + ): void |
| 116 | + { |
| 117 | + if ($assertType === 'type') { |
| 118 | + $expectedType = $args[0]; |
| 119 | + $expected = $expectedType->getValue(); |
| 120 | + $actualType = $args[1]; |
| 121 | + $actual = $actualType->describe(VerbosityLevel::precise()); |
| 122 | + $this->assertSame( |
| 123 | + $expected, |
| 124 | + $actual, |
| 125 | + sprintf('Expected type %s, got type %s in %s on line %d.', $expected, $actual, $file, $args[2]) |
| 126 | + ); |
| 127 | + } elseif ($assertType === 'variableCertainty') { |
| 128 | + $expectedCertainty = $args[0]; |
| 129 | + $actualCertainty = $args[1]; |
| 130 | + $variableName = $args[2]; |
| 131 | + $this->assertTrue( |
| 132 | + $expectedCertainty->equals($actualCertainty), |
| 133 | + sprintf('Expected %s, actual certainty of variable $%s is %s', $expectedCertainty->describe(), $variableName, $actualCertainty->describe()) |
| 134 | + ); |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + /** |
| 139 | + * @param string $file |
| 140 | + * @return array<string, mixed[]> |
| 141 | + */ |
| 142 | + public function gatherAssertTypes(string $file): array |
| 143 | + { |
| 144 | + $asserts = []; |
| 145 | + $this->processFile($file, function (Node $node, Scope $scope) use (&$asserts, $file): void { |
| 146 | + if (!$node instanceof Node\Expr\FuncCall) { |
| 147 | + return; |
| 148 | + } |
| 149 | + |
| 150 | + $nameNode = $node->name; |
| 151 | + if (!$nameNode instanceof Name) { |
| 152 | + return; |
| 153 | + } |
| 154 | + |
| 155 | + $functionName = $nameNode->toString(); |
| 156 | + if ($functionName === 'PHPStan\\Analyser\\assertType') { |
| 157 | + $expectedType = $scope->getType($node->args[0]->value); |
| 158 | + $actualType = $scope->getType($node->args[1]->value); |
| 159 | + $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; |
| 160 | + } elseif ($functionName === 'PHPStan\\Analyser\\assertNativeType') { |
| 161 | + $expectedType = $scope->getNativeType($node->args[0]->value); |
| 162 | + $actualType = $scope->getNativeType($node->args[1]->value); |
| 163 | + $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; |
| 164 | + } elseif ($functionName === 'PHPStan\\Analyser\\assertVariableCertainty') { |
| 165 | + $certainty = $node->args[0]->value; |
| 166 | + if (!$certainty instanceof StaticCall) { |
| 167 | + $this->fail(sprintf('First argument of %s() must be TrinaryLogic call', $functionName)); |
| 168 | + } |
| 169 | + if (!$certainty->class instanceof Node\Name) { |
| 170 | + $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); |
| 171 | + } |
| 172 | + |
| 173 | + if ($certainty->class->toString() !== 'PHPStan\\TrinaryLogic') { |
| 174 | + $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); |
| 175 | + } |
| 176 | + |
| 177 | + if (!$certainty->name instanceof Node\Identifier) { |
| 178 | + $this->fail(sprintf('ERROR: Invalid TrinaryLogic call.')); |
| 179 | + } |
| 180 | + |
| 181 | + // @phpstan-ignore-next-line |
| 182 | + $expectedertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); |
| 183 | + $variable = $node->args[1]->value; |
| 184 | + if (!$variable instanceof Node\Expr\Variable) { |
| 185 | + $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); |
| 186 | + } |
| 187 | + if (!is_string($variable->name)) { |
| 188 | + $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); |
| 189 | + } |
| 190 | + |
| 191 | + $actualCertaintyValue = $scope->hasVariableType($variable->name); |
| 192 | + $assert = ['variableCertainty', $file, $expectedertaintyValue, $actualCertaintyValue, $variable->name]; |
| 193 | + } else { |
| 194 | + return; |
| 195 | + } |
| 196 | + |
| 197 | + if (count($node->args) !== 2) { |
| 198 | + $this->fail(sprintf( |
| 199 | + 'ERROR: Wrong %s() call on line %d.', |
| 200 | + $functionName, |
| 201 | + $node->getLine() |
| 202 | + )); |
| 203 | + } |
| 204 | + |
| 205 | + $asserts[$file . ':' . $node->getLine()] = $assert; |
| 206 | + }); |
| 207 | + |
| 208 | + return $asserts; |
| 209 | + } |
| 210 | + |
100 | 211 | /** @return string[] */
|
101 | 212 | protected function getAdditionalAnalysedFiles(): array
|
102 | 213 | {
|
|
0 commit comments