优化生成的 C++ 代码体积,减少无效行

master
韩天峰 5 days ago
parent a08f73c995
commit 4b8d0eb685
  1. 7
      phpunit/code/generated-code-comments.php
  2. 14
      phpunit/code/generated-code-use-spacing.php
  3. 12
      phpunit/code/native-property-write-conversion.php
  4. 2
      phpunit/src/ClassTest.php
  5. 149
      phpunit/src/EmptyTranslationUnitTest.php
  6. 66
      phpunit/src/GeneratedCodeCommentTest.php
  7. 4
      phpunit/src/NativePropertyTest.php
  8. 40
      phpunit/src/TypeCheckGeneratorTest.php
  9. 10
      src/Build/SourcePipelineTrait.php
  10. 7
      src/CompilerBase.php
  11. 53
      src/Generator/TypeCheckGenerator.php
  12. 4
      src/Parser/AssignOpTrait.php
  13. 2
      src/Parser/ConditionalControlTrait.php
  14. 2
      src/Parser/FunctionCallTrait.php
  15. 6
      src/Parser/MethodCallTrait.php
  16. 6
      src/Parser/NullsafeAccessTrait.php
  17. 6
      src/Parser/PropertyAccessTrait.php
  18. 7
      src/Parser/SelectionExpressionTrait.php
  19. 5
      src/Resolver/DeclarationSymbolTrait.php
  20. 35
      src/Translator.php

@ -0,0 +1,7 @@
<?php
function generated_code_comments(?stdClass $object): mixed
{
$object?->dynamicMethod();
return $object;
}

@ -0,0 +1,14 @@
<?php
namespace GeneratedCodeUseSpacing;
use Example\First;
use Example\Second;
use Example\Third;
use Example\Fourth;
function createAnonymousObject(): object
{
return new class {
};
}

@ -4,10 +4,20 @@ use native_types;
class NativePropertyWriteConversionBox class NativePropertyWriteConversionBox
{ {
public int $value = 0; public int $value = 0;
public string $name = '';
public array $items = [];
} }
function native_property_write_conversion(NativePropertyWriteConversionBox $box, int $nativeValue, $dynamicValue): void function native_property_write_conversion(
NativePropertyWriteConversionBox $box,
int $nativeValue,
$dynamicValue,
$dynamicName,
$dynamicItems,
): void
{ {
$box->value = $nativeValue; $box->value = $nativeValue;
$box->value = $dynamicValue; $box->value = $dynamicValue;
$box->name = $dynamicName;
$box->items = $dynamicItems;
} }

@ -557,7 +557,7 @@ class ClassTest extends \BaseTest
$cppFile = $compiler->convertFile($testFile); $cppFile = $compiler->convertFile($testFile);
$this->assertStringContainsString( $this->assertStringContainsString(
'// Stmt_Expression(Expr_StaticCall)', 'this_.call(php_get_persistent_method(',
file_get_contents($cppFile), file_get_contents($cppFile),
); );
} }

@ -0,0 +1,149 @@
<?php
use TypePhp\CompilerTest;
final class EmptyTranslationUnitTest extends \BaseTest
{
private string $projectDir;
private CompilerTest $compiler;
protected function setUp(): void
{
parent::setUp();
$this->projectDir = sys_get_temp_dir() . '/typephp_empty_translation_' . bin2hex(random_bytes(6));
mkdir($this->projectDir, 0777, true);
global $translator;
$this->compiler = CompilerTest::create($this->projectDir);
$translator = $this->compiler;
}
protected function tearDown(): void
{
$this->removeDirectory($this->projectDir);
parent::tearDown();
}
public function testTraitOnlySourceIsNotAddedToCompilation(): void
{
$trait = $this->writeSource('CompileTimeOnlyTrait.php', <<<'PHP'
<?php
trait CompileTimeOnlyTrait
{
public function answer(): int
{
return 42;
}
}
PHP);
$program = $this->writeSource('program.php', <<<'PHP'
<?php
function main(): void
{
}
PHP);
$files = [$trait, $program];
$this->compiler->addFiles($files);
foreach ($files as $file) {
$this->compiler->prepareFile($file);
}
$traitCpp = $this->compiler->getCppFile($trait);
$traitObject = $this->compiler->getObjectFile($traitCpp);
if (!is_dir(dirname($traitCpp))) {
mkdir(dirname($traitCpp), 0777, true);
}
file_put_contents($traitCpp, 'stale translation unit');
file_put_contents($traitObject, 'stale object');
file_put_contents($traitObject . '.typephp-cache', 'stale metadata');
$sources = $this->compiler->convert($files);
self::assertNotContains($traitCpp, $sources);
self::assertContains($this->compiler->getCppFile($program), $sources);
self::assertFileDoesNotExist($traitCpp);
self::assertFileDoesNotExist($traitObject);
self::assertFileDoesNotExist($traitObject . '.typephp-cache');
self::assertFileExists($this->compiler->getArgInfoHeaderFile($trait));
}
public function testFileContainingTraitAndFunctionStillEmitsTranslationUnit(): void
{
$source = $this->writeSource('mixed.php', <<<'PHP'
<?php
trait MixedDeclarationTrait
{
public function value(): int
{
return 1;
}
}
function mixedDeclarationFunction(): int
{
return 7;
}
PHP);
$this->compiler->addFiles([$source]);
$this->compiler->prepareFile($source);
$cppFile = $this->compiler->convertFile($source);
self::assertNotNull($cppFile);
self::assertFileExists($cppFile);
self::assertStringContainsString('return php::toInt(7L);', file_get_contents($cppFile));
}
public function testCompileTimeOnlyLibraryStillEmitsExtensionSource(): void
{
$source = $this->writeSource('LibraryTrait.php', <<<'PHP'
<?php
trait LibraryTrait
{
public function value(): int
{
return 1;
}
}
PHP);
$this->compiler->setBuildMode('lib');
$this->compiler->setOutputPath($this->projectDir . '/app');
$this->compiler->addFiles([$source]);
$this->compiler->prepareFile($source);
$sources = $this->compiler->convert([$source]);
self::assertCount(1, $sources);
self::assertStringContainsString('extension-app.cc', $sources[0]);
self::assertFileExists($sources[0]);
self::assertFileDoesNotExist($this->compiler->getCppFile($source));
}
private function writeSource(string $name, string $source): string
{
$file = $this->projectDir . '/' . $name;
file_put_contents($file, $source);
return $file;
}
private function removeDirectory(string $directory): void
{
if (!is_dir($directory)) {
return;
}
foreach (array_diff(scandir($directory), ['.', '..']) as $entry) {
$path = $directory . '/' . $entry;
if (is_dir($path)) {
$this->removeDirectory($path);
} else {
unlink($path);
}
}
rmdir($directory);
}
}

@ -0,0 +1,66 @@
<?php
use TypePhp\CompilerTest;
final class GeneratedCodeCommentTest extends BaseTest
{
private function compileProbe(bool $debug): string
{
global $translator;
$compiler = CompilerTest::create(ROOT_PATH);
$translator = $compiler;
if ($debug) {
$property = new ReflectionProperty($compiler, 'debug');
$property->setValue($compiler, true);
}
$source = ROOT_PATH . '/phpunit/code/generated-code-comments.php';
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$generated = $compiler->convertFile($source);
$code = file_get_contents($generated);
self::assertIsString($code);
return $code;
}
private function compileFile(string $file): string
{
global $translator;
$compiler = CompilerTest::create(ROOT_PATH);
$translator = $compiler;
$source = ROOT_PATH . '/phpunit/code/' . $file;
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$generated = $compiler->convertFile($source);
self::assertNotNull($generated);
$code = file_get_contents($generated);
self::assertIsString($code);
return $code;
}
public function testReleaseCodeOmitsCompilerExplanatoryComments(): void
{
$code = $this->compileProbe(false);
self::assertStringNotContainsString('// Stmt_', $code);
self::assertStringNotContainsString('// Nullsafe Operator:', $code);
self::assertStringNotContainsString('// Method Call:', $code);
}
public function testDebugCodeKeepsCompilerExplanatoryComments(): void
{
$code = $this->compileProbe(true);
self::assertStringContainsString('// Stmt_', $code);
self::assertStringContainsString('// Nullsafe Operator:', $code);
}
public function testCompileTimeUseDeclarationsDoNotEmitBlankLines(): void
{
$code = $this->compileFile('generated-code-use-spacing.php');
self::assertDoesNotMatchRegularExpression('/0,};(?:[ \t]*\R){3,}/', $code);
}
}

@ -100,7 +100,11 @@ class NativePropertyTest extends \BaseTest
$code = file_get_contents($outputFile); $code = file_get_contents($outputFile);
$this->assertStringContainsString(' = nativeValue;', $code); $this->assertStringContainsString(' = nativeValue;', $code);
$this->assertStringContainsString(' = php::toIntExact(dynamicValue, "NativePropertyWriteConversionBox::$value");', $code); $this->assertStringContainsString(' = php::toIntExact(dynamicValue, "NativePropertyWriteConversionBox::$value");', $code);
$this->assertStringContainsString(' = php::toStringExact(dynamicName, "NativePropertyWriteConversionBox::$name");', $code);
$this->assertStringContainsString(' = php::toArrayExact(dynamicItems, "NativePropertyWriteConversionBox::$items");', $code);
$this->assertStringNotContainsString(' = php::toInt(nativeValue);', $code); $this->assertStringNotContainsString(' = php::toInt(nativeValue);', $code);
$this->assertStringNotContainsString('php::toString(([&]() -> php::Var', $code);
$this->assertStringNotContainsString('php::toArray(([&]() -> php::Var', $code);
} }
public function testNativeThisPropertyWriteUsesExactHelperOnNativeReference(): void public function testNativeThisPropertyWriteUsesExactHelperOnNativeReference(): void

@ -4,6 +4,7 @@ use TypePhp\Entity\ArgInfo;
use TypePhp\CompilerTest; use TypePhp\CompilerTest;
use TypePhp\Entity\ClassDef; use TypePhp\Entity\ClassDef;
use TypePhp\Entity\FunctionDef; use TypePhp\Entity\FunctionDef;
use TypePhp\Type;
class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
{ {
@ -30,6 +31,7 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$argInfo->name = 'value'; $argInfo->name = 'value';
$argInfo->typeStr = 'int|string'; $argInfo->typeStr = 'int|string';
$this->setProtectedProperty($compiler, 'noLiteralStrings', true);
$functionDef->returnTypeCheck = [['kind' => 'isInt'], ['kind' => 'isString']]; $functionDef->returnTypeCheck = [['kind' => 'isInt'], ['kind' => 'isString']];
$functionDef->returnTypeStr = 'int|string'; $functionDef->returnTypeStr = 'int|string';
@ -41,7 +43,10 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$returnCode = $this->invokeMethod($compiler, 'genUnionReturnCheck', ['retval']); $returnCode = $this->invokeMethod($compiler, 'genUnionReturnCheck', ['retval']);
$this->assertSame('Foo\\Bar\\Demo::run', $callableName); $this->assertSame('Foo\\Bar\\Demo::run', $callableName);
$this->assertStringContainsString('Foo\\\\Bar\\\\Demo::run(): Argument #', $paramExpr); $this->assertStringContainsString('php::throwArgumentTypeError(', $paramExpr);
$this->assertStringContainsString('Foo\\\\Bar\\\\Demo::run', $paramExpr);
$this->assertStringNotContainsString('must be of type', $paramExpr);
$this->assertStringContainsString('php::throwReturnTypeError(', $returnCode);
$this->assertStringContainsString('Foo\\\\Bar\\\\Demo::run', $returnCode); $this->assertStringContainsString('Foo\\\\Bar\\\\Demo::run', $returnCode);
} }
@ -53,6 +58,7 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$argInfo->name = 'value'; $argInfo->name = 'value';
$argInfo->typeStr = 'int|string'; $argInfo->typeStr = 'int|string';
$this->setProtectedProperty($compiler, 'noLiteralStrings', true);
$functionDef->returnTypeCheck = [['kind' => 'isInt'], ['kind' => 'isString']]; $functionDef->returnTypeCheck = [['kind' => 'isInt'], ['kind' => 'isString']];
$functionDef->returnTypeStr = 'int|string'; $functionDef->returnTypeStr = 'int|string';
@ -64,7 +70,37 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$returnCode = $this->invokeMethod($compiler, 'genUnionReturnCheck', ['retval']); $returnCode = $this->invokeMethod($compiler, 'genUnionReturnCheck', ['retval']);
$this->assertSame('Foo\\Bar\\run', $callableName); $this->assertSame('Foo\\Bar\\run', $callableName);
$this->assertStringContainsString('Foo\\\\Bar\\\\run(): Argument #', $paramExpr); $this->assertStringContainsString('php::throwArgumentTypeError(', $paramExpr);
$this->assertStringContainsString('Foo\\\\Bar\\\\run', $paramExpr);
$this->assertStringNotContainsString('must be of type', $paramExpr);
$this->assertStringContainsString('php::throwReturnTypeError(', $returnCode);
$this->assertStringContainsString('Foo\\\\Bar\\\\run', $returnCode); $this->assertStringContainsString('Foo\\\\Bar\\\\run', $returnCode);
} }
public function testStrictScalarChecksKeepTheFastCheckAndDelegateColdErrors(): void
{
$compiler = CompilerTest::create(ROOT_PATH);
$functionDef = new FunctionDef('run', 'php::Int', 'Foo\\Bar');
$argInfo = new ArgInfo();
$argInfo->name = 'value';
$argInfo->phpName = 'value';
$argInfo->type = Type::INT;
$this->setProtectedProperty($compiler, 'classDef', null);
$this->setProtectedProperty($compiler, 'functionDef', $functionDef);
$paramCode = $this->invokeMethod(
$compiler,
'genStrictScalarParamCheck',
[$argInfo, 'raw_value', 'Foo\\Bar\\run', '1'],
);
$returnCode = $this->invokeMethod($compiler, 'genStrictScalarReturnCheck', ['retval', Type::INT]);
$this->assertStringContainsString('raw_value.isInt()', $paramCode);
$this->assertStringContainsString('php::throwArgumentTypeError(', $paramCode);
$this->assertStringNotContainsString('must be of type', $paramCode);
$this->assertStringContainsString('retval.isInt()', $returnCode);
$this->assertStringContainsString('php::throwReturnTypeError(', $returnCode);
$this->assertStringNotContainsString('must be of type', $returnCode);
}
} }

@ -250,6 +250,7 @@ trait SourcePipelineTrait
public function convert(array $files): array public function convert(array $files): array
{ {
$sourceFiles = []; $sourceFiles = [];
$validSourceCount = 0;
// 生成 C++ 文件 // 生成 C++ 文件
foreach ($files as $k => $file) { foreach ($files as $k => $file) {
try { try {
@ -260,7 +261,10 @@ trait SourcePipelineTrait
} else { } else {
continue; continue;
} }
$validSourceCount++;
if ($cppFile !== null) {
$sourceFiles[] = $cppFile; $sourceFiles[] = $cppFile;
}
} catch (Unsupported $e) { } catch (Unsupported $e) {
echo ' unsupported syntax: ' . $e->getMessage() . "\n"; echo ' unsupported syntax: ' . $e->getMessage() . "\n";
echo ' skip: ' . $file . "\n"; echo ' skip: ' . $file . "\n";
@ -268,7 +272,11 @@ trait SourcePipelineTrait
} }
} }
if (empty($sourceFiles)) { // A valid PHP input may intentionally emit no standalone translation
// unit (for example a compile-time trait or an interface). The shared
// extension source still carries its runtime metadata, so only reject
// an input set in which no supported source was converted at all.
if ($validSourceCount === 0) {
$this->stop('No valid source file found'); $this->stop('No valid source file found');
} }

@ -1590,7 +1590,7 @@ class CompilerBase implements PropertyAccessContext
$class = 'Stmt_Expression(' . $v->expr->getType() . ')'; $class = 'Stmt_Expression(' . $v->expr->getType() . ')';
} }
return $this->getIndent() . '// ' . $class . ' [' . $v->getStartLine() . ':' . $v->getEndLine() . ']'; return '// ' . $class . ' [' . $v->getStartLine() . ':' . $v->getEndLine() . ']';
} }
/** /**
@ -1698,8 +1698,10 @@ class CompilerBase implements PropertyAccessContext
$this->context->afterStmtLines = []; $this->context->afterStmtLines = [];
$result = ''; $result = '';
$this->writeLog('Line ' . $this->getLine($v) . ': ' . $class); $this->writeLog('Line ' . $this->getLine($v) . ': ' . $class);
if ($this->debug) {
$lines[] = $this->genDebugInfo($v); $lines[] = $this->genDebugInfo($v);
$lines[] = $this->getComment($v, $class); $lines[] = $this->getComment($v, $class);
}
switch ($class) { switch ($class) {
case 'Stmt_Expression': case 'Stmt_Expression':
$v->expr->setAttribute(self::ATTR_STATEMENT_EXPRESSION, true); $v->expr->setAttribute(self::ATTR_STATEMENT_EXPRESSION, true);
@ -3681,6 +3683,9 @@ class CompilerBase implements PropertyAccessContext
protected function formatCppLineComment(string $label, string $text): string protected function formatCppLineComment(string $label, string $text): string
{ {
if (!$this->debug) {
return '';
}
$lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $text)); $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $text));
$padding = str_repeat(' ', strlen($label)); $padding = str_repeat(' ', strlen($label));
$comments = []; $comments = [];

@ -58,15 +58,10 @@ trait TypeCheckGenerator
} }
$paramName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name); $paramName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name);
$format = $this->genCharPtr($callableName . '(): Argument #', true) $throwExpr = 'php::throwArgumentTypeError(' . $valueExpr . ', '
. ' ZEND_LONG_FMT ' . $this->getLiteralString($callableName) . ', ' . $argNoExpr . ', '
. $this->genCharPtr( . $this->getLiteralString($paramName) . ', '
' ($' . $paramName . ') must be of type ' . $this->strictScalarTypeName($argInfo->type) . $this->getLiteralString($this->strictScalarTypeName($argInfo->type)) . ')';
. ', %s given',
true
);
$throwExpr = 'php::throwExceptionEx(zend_ce_type_error, 0, ' . $format . ', '
. $argNoExpr . ', ' . $valueExpr . '.typeStr())';
$code = $this->getIndent() . 'if (UNEXPECTED(!(' $code = $this->getIndent() . 'if (UNEXPECTED(!('
. $this->genStrictScalarCondition($valueExpr, $argInfo->type) . '))) {' . PHP_EOL; . $this->genStrictScalarCondition($valueExpr, $argInfo->type) . '))) {' . PHP_EOL;
@ -84,17 +79,13 @@ trait TypeCheckGenerator
} }
$fnName = $this->getTypeCheckCallableName(); $fnName = $this->getTypeCheckCallableName();
$format = $this->genCharPtr(
$fnName . '(): Return value must be of type ' . $this->strictScalarTypeName($returnType)
. ', %s returned',
true
);
$code = $this->getIndent() . 'if (UNEXPECTED(!(' $code = $this->getIndent() . 'if (UNEXPECTED(!('
. $this->genStrictScalarCondition($valueExpr, $returnType) . '))) {' . PHP_EOL; . $this->genStrictScalarCondition($valueExpr, $returnType) . '))) {' . PHP_EOL;
$this->indentLevel++; $this->indentLevel++;
$code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_type_error, 0, ' $code .= $this->getIndent() . 'php::throwReturnTypeError(' . $valueExpr . ', '
. $format . ', ' . $valueExpr . '.typeStr());' . PHP_EOL; . $this->getLiteralString($fnName) . ', '
. $this->getLiteralString($this->strictScalarTypeName($returnType)) . ', '
. $this->escapeBool(true) . ');' . PHP_EOL;
$this->indentLevel--; $this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL; $code .= $this->getIndent() . '}' . PHP_EOL;
return $code; return $code;
@ -392,11 +383,9 @@ trait TypeCheckGenerator
{ {
$fnName = $this->getTypeCheckCallableName(); $fnName = $this->getTypeCheckCallableName();
$paramName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name); $paramName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name);
$format = $this->genCharPtr($fnName . '(): Argument #', true) return 'php::throwArgumentTypeError(' . $valueExpr . ', '
. ' ZEND_LONG_FMT ' . $this->getLiteralString($fnName) . ', ' . $argNoExpr . ', '
. $this->genCharPtr(' ($' . $paramName . ') must be of type ' . $argInfo->typeStr . ', %s given', true); . $this->getLiteralString($paramName) . ', ' . $this->getLiteralString($argInfo->typeStr) . ')';
return 'php::throwExceptionEx(zend_ce_type_error, 0, ' . $format . ', '
. $argNoExpr . ', ' . $valueExpr . '.typeStr())';
} }
protected function genUnionReturnCheck(string $varName): string protected function genUnionReturnCheck(string $varName): string
@ -421,12 +410,12 @@ trait TypeCheckGenerator
$fnName = $this->getTypeCheckCallableName(); $fnName = $this->getTypeCheckCallableName();
$typeStr = $this->functionDef->returnTypeStr; $typeStr = $this->functionDef->returnTypeStr;
$format = $this->genCharPtr($fnName . '(): Return value must be of type ' . $typeStr . ', %s given', true);
$code = $this->genCompositeIntToFloatCoercion($varName, $typeCheck); $code = $this->genCompositeIntToFloatCoercion($varName, $typeCheck);
$code .= $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL; $code .= $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL;
$this->indentLevel++; $this->indentLevel++;
$code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_type_error, 0, ' . $format . ', ' . $varName . '.typeStr());' . PHP_EOL; $code .= $this->getIndent() . 'php::throwReturnTypeError(' . $varName . ', '
. $this->getLiteralString($fnName) . ', ' . $this->getLiteralString($typeStr) . ', '
. $this->escapeBool(false) . ');' . PHP_EOL;
$this->indentLevel--; $this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL; $code .= $this->getIndent() . '}' . PHP_EOL;
@ -515,11 +504,9 @@ trait TypeCheckGenerator
protected function genClosureParamTypeErrorExpr(ArgInfo $argInfo, string $valueExpr, string $argNoExpr): string protected function genClosureParamTypeErrorExpr(ArgInfo $argInfo, string $valueExpr, string $argNoExpr): string
{ {
$paramName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name); $paramName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name);
$format = $this->genCharPtr('{closure}(): Argument #', true) return 'php::throwArgumentTypeError(' . $valueExpr . ', '
. ' ZEND_LONG_FMT ' . $this->getLiteralString('{closure}') . ', ' . $argNoExpr . ', '
. $this->genCharPtr(' ($' . $paramName . ') must be of type ' . $argInfo->typeStr . ', %s given', true); . $this->getLiteralString($paramName) . ', ' . $this->getLiteralString($argInfo->typeStr) . ')';
return 'php::throwExceptionEx(zend_ce_type_error, 0, ' . $format . ', '
. $argNoExpr . ', ' . $valueExpr . '.typeStr())';
} }
protected function genClosureReturnCheck(string $varName): string protected function genClosureReturnCheck(string $varName): string
@ -542,12 +529,12 @@ trait TypeCheckGenerator
$orExpr = implode(' || ', $conditions); $orExpr = implode(' || ', $conditions);
$typeStr = $this->context->closureReturnTypeStr; $typeStr = $this->context->closureReturnTypeStr;
$format = $this->genCharPtr('{closure}(): Return value must be of type ' . $typeStr . ', %s given', true);
$code = $this->genCompositeIntToFloatCoercion($varName, $typeCheck); $code = $this->genCompositeIntToFloatCoercion($varName, $typeCheck);
$code .= $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL; $code .= $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL;
$this->indentLevel++; $this->indentLevel++;
$code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_type_error, 0, ' . $format . ', ' . $varName . '.typeStr());' . PHP_EOL; $code .= $this->getIndent() . 'php::throwReturnTypeError(' . $varName . ', '
. $this->getLiteralString('{closure}') . ', ' . $this->getLiteralString($typeStr) . ', '
. $this->escapeBool(false) . ');' . PHP_EOL;
$code .= $this->getIndent() . 'return php::null;' . PHP_EOL; $code .= $this->getIndent() . 'return php::null;' . PHP_EOL;
$this->indentLevel--; $this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL; $code .= $this->getIndent() . '}' . PHP_EOL;

@ -678,7 +678,7 @@ trait AssignOpTrait
$leftExprType = $this->detectTypeOfExpr($left); $leftExprType = $this->detectTypeOfExpr($left);
$rightExprType = $this->detectTypeOfExpr($right); $rightExprType = $this->detectTypeOfExpr($right);
if ($propertyWriteTarget !== null && ($propertyDef = $this->getNativePropertyDef($left)) !== null) { if ($propertyWriteTarget !== null && ($propertyDef = $this->getNativePropertyDef($left)) !== null) {
$effectiveRightType = $rightExprType === Type::VAR && $this->getNativeScalarPropertyTypeCheckHelper($propertyDef) !== null $effectiveRightType = $rightExprType === Type::VAR && $this->getFixedPropertyTypeCheckHelper($propertyDef) !== null
? $propertyDef->type ? $propertyDef->type
: $rightExprType; : $rightExprType;
return $var . ' = ' . $this->convertNativePropertyWriteExpr($propertyDef->type, $effectiveRightType, $rightExpr); return $var . ' = ' . $this->convertNativePropertyWriteExpr($propertyDef->type, $effectiveRightType, $rightExpr);
@ -1007,7 +1007,7 @@ trait AssignOpTrait
if ($rightType === Type::VAR) { if ($rightType === Type::VAR) {
$rightExpr = $this->wrapObjectPropertyAssignTypeCheck($node->var, $node->expr, $rightExpr); $rightExpr = $this->wrapObjectPropertyAssignTypeCheck($node->var, $node->expr, $rightExpr);
} }
$effectiveRightType = $rightType === Type::VAR && $this->getNativeScalarPropertyTypeCheckHelper($def) !== null $effectiveRightType = $rightType === Type::VAR && $this->getFixedPropertyTypeCheckHelper($def) !== null
? $def->type ? $def->type
: $rightType; : $rightType;

@ -18,7 +18,7 @@ trait ConditionalControlTrait
$arms[] = [$elseif->cond, $elseif->stmts]; $arms[] = [$elseif->cond, $elseif->stmts];
} }
return $this->parseBeforeStmtLines() . PHP_EOL . $this->parseIfChain($arms, $v->else, 0) . PHP_EOL; return $this->parseBeforeStmtLines() . PHP_EOL . $this->getIndent() . $this->parseIfChain($arms, $v->else, 0) . PHP_EOL;
} }
protected function parseIfChain(array $arms, ?Node\Stmt\Else_ $else, int $index): string protected function parseIfChain(array $arms, ?Node\Stmt\Else_ $else, int $index): string

@ -190,7 +190,9 @@ trait FunctionCallTrait
} }
$placeHolder = $this->identifierToStr($expr->name); $placeHolder = $this->identifierToStr($expr->name);
$fn = $this->getFuncPtr($name); $fn = $this->getFuncPtr($name);
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment('Func Call: ', $name . '()'); $this->context->beforeStmtLines[] = $this->formatCppLineComment('Func Call: ', $name . '()');
}
} else { } else {
$tmpVar = $this->addTmpVar(Type::VAR); $tmpVar = $this->addTmpVar(Type::VAR);
$this->context->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';'; $this->context->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';';

@ -580,10 +580,12 @@ trait MethodCallTrait
} }
$this->fatalError($expr, "Cannot call method `{$methodName}()` on variable of type {$type}"); $this->fatalError($expr, "Cannot call method `{$methodName}()` on variable of type {$type}");
} }
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment( $this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Method Call: ', 'Method Call: ',
$object . '->' . $this->parseIdentifier($expr->name) . '()' $object . '->' . $this->parseIdentifier($expr->name) . '()'
); );
}
$nativeFunc = false; $nativeFunc = false;
try { try {
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name)); $nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
@ -808,10 +810,12 @@ trait MethodCallTrait
$method = $this->parseIdentifier($expr->name); $method = $this->parseIdentifier($expr->name);
$methodPtr = $this->identifierToStr($expr->name, literal: true); $methodPtr = $this->identifierToStr($expr->name, literal: true);
$fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')'; $fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')';
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment( $this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ', 'Static Method Call: ',
'static::' . $method . '()' 'static::' . $method . '()'
); );
}
$placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]); $placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]);
// 用于在按引用参数检测时解析方法签名(late static binding 在当前类层级中解析) // 用于在按引用参数检测时解析方法签名(late static binding 在当前类层级中解析)
$rtFunc = $method; $rtFunc = $method;
@ -829,10 +833,12 @@ trait MethodCallTrait
$method = $this->parseIdentifier($expr->name); $method = $this->parseIdentifier($expr->name);
$rtFunc = $method; $rtFunc = $method;
$rtClass = $class; $rtClass = $class;
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment( $this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ', 'Static Method Call: ',
$class . '::' . $method . '()' $class . '::' . $method . '()'
); );
}
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)]; $callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)];

@ -45,7 +45,9 @@ trait NullsafeAccessTrait
$list = []; $list = [];
$ownedTmpVars = []; $ownedTmpVars = [];
$comment = $this->formatCppLineComment('Nullsafe Operator: ', $this->printer->prettyPrint([$expr])); $comment = $this->debug
? $this->formatCppLineComment('Nullsafe Operator: ', $this->printer->prettyPrint([$expr])) . PHP_EOL
: '';
while (1) { while (1) {
if ($expr instanceof Expr\NullsafePropertyFetch) { if ($expr instanceof Expr\NullsafePropertyFetch) {
@ -83,7 +85,7 @@ trait NullsafeAccessTrait
$last = array_key_last($list); $last = array_key_last($list);
$tmpFn = $this->genTmpVarName(); $tmpFn = $this->genTmpVarName();
$code = $comment . PHP_EOL . 'auto ' . $tmpFn . ' = [&]() -> ' . Type::VAR . '{' . PHP_EOL; $code = $comment . 'auto ' . $tmpFn . ' = [&]() -> ' . Type::VAR . '{' . PHP_EOL;
foreach ($list as $key => $item) { foreach ($list as $key => $item) {
$tmpVar = $this->addTmpVar($key !== $last ? Type::OBJECT : Type::VAR); $tmpVar = $this->addTmpVar($key !== $last ? Type::OBJECT : Type::VAR);

@ -661,7 +661,7 @@ trait PropertyAccessTrait
if ($rightType !== Type::VAR && $this->canAssignStaticTypeToObjectProperty($def, $rightType)) { if ($rightType !== Type::VAR && $this->canAssignStaticTypeToObjectProperty($def, $rightType)) {
return $rightExpr; return $rightExpr;
} }
if ($rightType === Type::VAR && ($helper = $this->getNativeScalarPropertyTypeCheckHelper($def)) !== null) { if ($rightType === Type::VAR && ($helper = $this->getFixedPropertyTypeCheckHelper($def)) !== null) {
return $helper . '(' . $rightExpr . ', ' . $this->genCharPtr($this->getObjectPropertyTypeCheckDisplayName($left), true) . ')'; return $helper . '(' . $rightExpr . ', ' . $this->genCharPtr($this->getObjectPropertyTypeCheckDisplayName($left), true) . ')';
} }
@ -745,7 +745,7 @@ trait PropertyAccessTrait
], true); ], true);
} }
protected function getNativeScalarPropertyTypeCheckHelper(PropertyDef $def): ?string protected function getFixedPropertyTypeCheckHelper(PropertyDef $def): ?string
{ {
if (!empty($def->typeCheck) || $def->class !== '' || $def->nullable) { if (!empty($def->typeCheck) || $def->class !== '' || $def->nullable) {
return null; return null;
@ -755,6 +755,8 @@ trait PropertyAccessTrait
Type::INT => 'php::toIntExact', Type::INT => 'php::toIntExact',
Type::FLOAT => 'php::toFloatExact', Type::FLOAT => 'php::toFloatExact',
Type::BOOL => 'php::toBoolExact', Type::BOOL => 'php::toBoolExact',
Type::STR => 'php::toStringExact',
Type::ARRAY => 'php::toArrayExact',
default => null, default => null,
}; };
} }

@ -286,8 +286,11 @@ trait SelectionExpressionTrait
$this->checkVarMustExist($right, $rightExpr); $this->checkVarMustExist($right, $rightExpr);
$tmpVar = $this->addTmpVar(Type::VAR); $tmpVar = $this->addTmpVar(Type::VAR);
$comment = $this->debug
? $this->formatCppLineComment('Expr: ', $this->printer->prettyPrintExpr($expr)) . PHP_EOL
: '';
if ($rightBeforeStmts || $rightAfterStmts) { if ($rightBeforeStmts || $rightAfterStmts) {
$code = $this->formatCppLineComment('Expr: ', $this->printer->prettyPrintExpr($expr)) . PHP_EOL . $code = $comment .
'if (' . $condExpr . ') {' . PHP_EOL . 'if (' . $condExpr . ') {' . PHP_EOL .
$this->getIndent() . $tmpVar . ' = ' . $leftExpr . ';' . PHP_EOL . $this->getIndent() . $tmpVar . ' = ' . $leftExpr . ';' . PHP_EOL .
'} else {' . PHP_EOL; '} else {' . PHP_EOL;
@ -305,7 +308,7 @@ trait SelectionExpressionTrait
$code .= '}'; $code .= '}';
$this->context->beforeStmtLines[] = $code; $this->context->beforeStmtLines[] = $code;
} else { } else {
$this->context->beforeStmtLines[] = $this->formatCppLineComment('Expr: ', $this->printer->prettyPrintExpr($expr)) . PHP_EOL . $this->context->beforeStmtLines[] = $comment .
$tmpVar . ' = ' . $condExpr . ' ? ' . $leftExpr . ' : ' . $rightExpr . ';'; $tmpVar . ' = ' . $condExpr . ' ? ' . $leftExpr . ' : ' . $rightExpr . ';';
} }
$expr->setAttribute('replace', $tmpVar); $expr->setAttribute('replace', $tmpVar);

@ -66,10 +66,8 @@ trait DeclarationSymbolTrait
return Type::VAR; return Type::VAR;
} }
protected function parseUse(Node\Stmt\Use_ $v2): void
protected function parseUse(Node\Stmt\Use_ $v2): string
{ {
$code = '';
foreach ($v2->uses as $use) { foreach ($v2->uses as $use) {
$id = $this->parseIdentifier($use->name); $id = $this->parseIdentifier($use->name);
$type = $use->type !== Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $v2->type; $type = $use->type !== Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $v2->type;
@ -107,7 +105,6 @@ trait DeclarationSymbolTrait
} }
} }
} }
return $code;
} }
protected function parseGroupUse(Node\Stmt\GroupUse $node): void protected function parseGroupUse(Node\Stmt\GroupUse $node): void

@ -532,7 +532,7 @@ class Translator extends Preprocessor
$this->formatCppCode($file); $this->formatCppCode($file);
} }
public function convertFile(string $file): string public function convertFile(string $file): ?string
{ {
$previousPhase = $this->enterCompilerPhase(self::PHASE_CONVERT); $previousPhase = $this->enterCompilerPhase(self::PHASE_CONVERT);
try { try {
@ -543,11 +543,15 @@ class Translator extends Preprocessor
try { try {
$cppCode = $this->doConvert($phpCode); $cppCode = $this->doConvert($phpCode);
$cppFile = $this->getCppFile($file); $cppFile = $this->getCppFile($file);
if ($cppCode === '') {
$this->removeEmptyTranslationUnitArtifacts($cppFile);
} else {
$this->save($cppCode, $cppFile); $this->save($cppCode, $cppFile);
}
$this->phpSrcFiles[] = $file; $this->phpSrcFiles[] = $file;
// 生成 stub 文件,依赖 convert 阶段的 use 等信息 // 生成 stub 文件,依赖 convert 阶段的 use 等信息
$this->genStubFile($this->file); $this->genStubFile($this->file);
return $cppFile; return $cppCode === '' ? null : $cppFile;
} catch (Redo $e) { } catch (Redo $e) {
continue; continue;
} }
@ -557,6 +561,20 @@ class Translator extends Preprocessor
} }
} }
/**
* Remove artifacts left by an earlier build when a PHP source no longer
* emits a C++ translation unit. Trait-only files are the common case.
*/
private function removeEmptyTranslationUnitArtifacts(string $cppFile): void
{
$objectFile = $this->getObjectFile($cppFile);
foreach ([$cppFile, $objectFile, $this->getMiscObjectCacheMetadataFile($objectFile)] as $artifact) {
if (is_file($artifact) && !unlink($artifact)) {
throw new \RuntimeException("Unable to remove stale generated artifact: {$artifact}");
}
}
}
public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string
{ {
return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); return implode(', ', $this->getRegisterClassFunctionCeList($classDef));
@ -2557,7 +2575,7 @@ CODE;
$cppCode .= $this->parseClass($v); $cppCode .= $this->parseClass($v);
break; break;
case 'Stmt_Use': case 'Stmt_Use':
$cppCode .= $this->parseUse($v) . PHP_EOL; $this->parseUse($v);
break; break;
case 'Stmt_GroupUse': case 'Stmt_GroupUse':
$this->parseGroupUse($v); $this->parseGroupUse($v);
@ -2601,6 +2619,15 @@ CODE;
foreach ($this->constData as $name => $data) { foreach ($this->constData as $name => $data) {
$constDataCode .= 'static const unsigned char ' . $name . '[] = {' . $data . '};' . PHP_EOL; $constDataCode .= 'static const unsigned char ' . $name . '[] = {' . $data . '};' . PHP_EOL;
} }
// Preparing and converting a compile-time-only source is still
// required for diagnostics, symbol collection and trait AST
// composition. Avoid creating a header-only .cc file when that work
// produced no C++ entity.
if (trim($constDataCode . $cppCode) === '') {
return '';
}
$constDataCode .= PHP_EOL; $constDataCode .= PHP_EOL;
return $this->genIncludeHeaderFiles() . $constDataCode . $cppCode; return $this->genIncludeHeaderFiles() . $constDataCode . $cppCode;
@ -2741,7 +2768,7 @@ CODE;
$code .= $this->parseFunction($v2) . PHP_EOL; $code .= $this->parseFunction($v2) . PHP_EOL;
break; break;
case 'Stmt_Use': case 'Stmt_Use':
$code .= $this->parseUse($v2) . PHP_EOL; $this->parseUse($v2);
break; break;
case 'Stmt_GroupUse': case 'Stmt_GroupUse':
$this->parseGroupUse($v2); $this->parseGroupUse($v2);

Loading…
Cancel
Save