- Implement static type validation for union, intersection, and nullable types - Add composite type assignment checking for parameters, return values, and property assignments - Introduce dynamic value wrapper tests to verify type checking behavior - Enhance error messaging for composite type mismatches - Update documentation to reflect improved static type analysis capabilities - Add proper restoration of compiler state after generator conversion errors - Improve parallel compilation process management and error handling - Add metadata caching for object files with PHP ABI compatibility checkspull/16/head
parent
35795be037
commit
9f6a13d5f7
12 changed files with 539 additions and 81 deletions
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
function broken_generator(): iterable |
||||||
|
{ |
||||||
|
$name = 'value'; |
||||||
|
yield $$name; |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests\Generator; |
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
use TypePhp\Exception\TestError; |
||||||
|
|
||||||
|
class FiberGeneratorTest extends TestCase |
||||||
|
{ |
||||||
|
public function testCompilerStateIsRestoredAfterGeneratorConversionError(): void |
||||||
|
{ |
||||||
|
$compiler = CompilerTest::create(ROOT_PATH); |
||||||
|
$file = __DIR__ . '/../../code/generator-conversion-error.php'; |
||||||
|
$compiler->addFiles([$file]); |
||||||
|
$compiler->prepareFile($file); |
||||||
|
|
||||||
|
try { |
||||||
|
$compiler->convertFile($file); |
||||||
|
$this->fail('The variable-variable expression should fail conversion'); |
||||||
|
} catch (TestError $e) { |
||||||
|
$this->assertStringContainsString('The `$$` syntax is not supported', $e->getMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
$reflection = new \ReflectionClass($compiler); |
||||||
|
$this->assertFalse($reflection->getProperty('inGeneratorBody')->getValue($compiler)); |
||||||
|
$this->assertSame(0, $reflection->getProperty('indentLevel')->getValue($compiler)); |
||||||
|
$this->assertSame('', $reflection->getProperty('function')->getValue($compiler)); |
||||||
|
$this->assertNull($reflection->getProperty('functionDef')->getValue($compiler)); |
||||||
|
$context = $reflection->getProperty('context')->getValue($compiler); |
||||||
|
$this->assertFalse($context->inClosure); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,107 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests; |
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
|
||||||
|
class ParallelCompileTest extends TestCase |
||||||
|
{ |
||||||
|
public function testWaitRetriesWhenInterrupted(): void |
||||||
|
{ |
||||||
|
$compiler = new ScriptedWaitCompiler([ |
||||||
|
[-1, 0, PCNTL_EINTR], |
||||||
|
[123, 0, 0], |
||||||
|
]); |
||||||
|
|
||||||
|
$this->assertSame([123, 0], $compiler->waitForTest()); |
||||||
|
$this->assertSame(2, $compiler->getWaitCallCount()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testWaitFailureOtherThanInterruptionThrows(): void |
||||||
|
{ |
||||||
|
$compiler = new ScriptedWaitCompiler([ |
||||||
|
[-1, 0, PCNTL_ECHILD], |
||||||
|
]); |
||||||
|
|
||||||
|
$this->expectException(\RuntimeException::class); |
||||||
|
$this->expectExceptionMessage('Failed to wait for compiler process'); |
||||||
|
$compiler->waitForTest(); |
||||||
|
} |
||||||
|
|
||||||
|
public function testSignaledChildIsNotSuccessful(): void |
||||||
|
{ |
||||||
|
$compiler = new ScriptedWaitCompiler([]); |
||||||
|
|
||||||
|
$this->assertTrue($compiler->statusSucceeded(0)); |
||||||
|
$this->assertFalse($compiler->statusSucceeded(1 << 8)); |
||||||
|
$this->assertFalse($compiler->statusSucceeded(SIGTERM)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testForkFailureStillReapsRunningCompilerProcesses(): void |
||||||
|
{ |
||||||
|
$compiler = new ScriptedWaitCompiler( |
||||||
|
[[101, 0, 0]], |
||||||
|
[101, -1] |
||||||
|
); |
||||||
|
|
||||||
|
try { |
||||||
|
$compiler->compileInParallelForTest(['first.cc', 'second.cc', 'third.cc'], 2); |
||||||
|
$this->fail('The fork failure should fail the parallel compilation'); |
||||||
|
} catch (\Exception $e) { |
||||||
|
$this->assertStringContainsString('second.cc', $e->getMessage()); |
||||||
|
$this->assertStringContainsString('third.cc', $e->getMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
$this->assertSame(1, $compiler->getWaitCallCount()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ScriptedWaitCompiler extends CompilerTest |
||||||
|
{ |
||||||
|
private int $waitCallCount = 0; |
||||||
|
private int $lastWaitError = 0; |
||||||
|
|
||||||
|
public function __construct(private array $waitResults, private array $forkResults = []) |
||||||
|
{ |
||||||
|
parent::__construct(ROOT_PATH); |
||||||
|
$this->noProgress = true; |
||||||
|
} |
||||||
|
|
||||||
|
protected function pcntlFork(): int |
||||||
|
{ |
||||||
|
return array_shift($this->forkResults); |
||||||
|
} |
||||||
|
|
||||||
|
protected function pcntlWait(?int &$status): int |
||||||
|
{ |
||||||
|
$this->waitCallCount++; |
||||||
|
[$pid, $status, $this->lastWaitError] = array_shift($this->waitResults); |
||||||
|
return $pid; |
||||||
|
} |
||||||
|
|
||||||
|
protected function pcntlLastError(): int |
||||||
|
{ |
||||||
|
return $this->lastWaitError; |
||||||
|
} |
||||||
|
|
||||||
|
public function waitForTest(): array |
||||||
|
{ |
||||||
|
return $this->waitForCompileChild(); |
||||||
|
} |
||||||
|
|
||||||
|
public function statusSucceeded(int $status): bool |
||||||
|
{ |
||||||
|
return $this->compileChildSucceeded($status); |
||||||
|
} |
||||||
|
|
||||||
|
public function getWaitCallCount(): int |
||||||
|
{ |
||||||
|
return $this->waitCallCount; |
||||||
|
} |
||||||
|
|
||||||
|
public function compileInParallelForTest(array $sourceFiles, int $jobs): array |
||||||
|
{ |
||||||
|
return $this->compileWithPcntl($sourceFiles, $jobs); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue