优化生成的 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. 12
      src/Build/SourcePipelineTrait.php
  10. 11
      src/CompilerBase.php
  11. 53
      src/Generator/TypeCheckGenerator.php
  12. 4
      src/Parser/AssignOpTrait.php
  13. 2
      src/Parser/ConditionalControlTrait.php
  14. 4
      src/Parser/FunctionCallTrait.php
  15. 30
      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. 37
      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
{
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 = $dynamicValue;
$box->name = $dynamicName;
$box->items = $dynamicItems;
}

@ -557,7 +557,7 @@ class ClassTest extends \BaseTest
$cppFile = $compiler->convertFile($testFile);
$this->assertStringContainsString(
'// Stmt_Expression(Expr_StaticCall)',
'this_.call(php_get_persistent_method(',
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);
$this->assertStringContainsString(' = nativeValue;', $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::toString(([&]() -> php::Var', $code);
$this->assertStringNotContainsString('php::toArray(([&]() -> php::Var', $code);
}
public function testNativeThisPropertyWriteUsesExactHelperOnNativeReference(): void

@ -4,6 +4,7 @@ use TypePhp\Entity\ArgInfo;
use TypePhp\CompilerTest;
use TypePhp\Entity\ClassDef;
use TypePhp\Entity\FunctionDef;
use TypePhp\Type;
class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
{
@ -30,6 +31,7 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$argInfo->name = 'value';
$argInfo->typeStr = 'int|string';
$this->setProtectedProperty($compiler, 'noLiteralStrings', true);
$functionDef->returnTypeCheck = [['kind' => 'isInt'], ['kind' => 'isString']];
$functionDef->returnTypeStr = 'int|string';
@ -41,7 +43,10 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$returnCode = $this->invokeMethod($compiler, 'genUnionReturnCheck', ['retval']);
$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);
}
@ -53,6 +58,7 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$argInfo->name = 'value';
$argInfo->typeStr = 'int|string';
$this->setProtectedProperty($compiler, 'noLiteralStrings', true);
$functionDef->returnTypeCheck = [['kind' => 'isInt'], ['kind' => 'isString']];
$functionDef->returnTypeStr = 'int|string';
@ -64,7 +70,37 @@ class TypeCheckGeneratorTest extends \PHPUnit\Framework\TestCase
$returnCode = $this->invokeMethod($compiler, 'genUnionReturnCheck', ['retval']);
$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);
}
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
{
$sourceFiles = [];
$validSourceCount = 0;
// 生成 C++ 文件
foreach ($files as $k => $file) {
try {
@ -260,7 +261,10 @@ trait SourcePipelineTrait
} else {
continue;
}
$sourceFiles[] = $cppFile;
$validSourceCount++;
if ($cppFile !== null) {
$sourceFiles[] = $cppFile;
}
} catch (Unsupported $e) {
echo ' unsupported syntax: ' . $e->getMessage() . "\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');
}

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

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

@ -678,7 +678,7 @@ trait AssignOpTrait
$leftExprType = $this->detectTypeOfExpr($left);
$rightExprType = $this->detectTypeOfExpr($right);
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
: $rightExprType;
return $var . ' = ' . $this->convertNativePropertyWriteExpr($propertyDef->type, $effectiveRightType, $rightExpr);
@ -1007,7 +1007,7 @@ trait AssignOpTrait
if ($rightType === Type::VAR) {
$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
: $rightType;

@ -18,7 +18,7 @@ trait ConditionalControlTrait
$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

@ -190,7 +190,9 @@ trait FunctionCallTrait
}
$placeHolder = $this->identifierToStr($expr->name);
$fn = $this->getFuncPtr($name);
$this->context->beforeStmtLines[] = $this->formatCppLineComment('Func Call: ', $name . '()');
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment('Func Call: ', $name . '()');
}
} else {
$tmpVar = $this->addTmpVar(Type::VAR);
$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->context->beforeStmtLines[] = $this->formatCppLineComment(
'Method Call: ',
$object . '->' . $this->parseIdentifier($expr->name) . '()'
);
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Method Call: ',
$object . '->' . $this->parseIdentifier($expr->name) . '()'
);
}
$nativeFunc = false;
try {
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
@ -808,10 +810,12 @@ trait MethodCallTrait
$method = $this->parseIdentifier($expr->name);
$methodPtr = $this->identifierToStr($expr->name, literal: true);
$fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')';
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ',
'static::' . $method . '()'
);
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ',
'static::' . $method . '()'
);
}
$placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]);
// 用于在按引用参数检测时解析方法签名(late static binding 在当前类层级中解析)
$rtFunc = $method;
@ -829,10 +833,12 @@ trait MethodCallTrait
$method = $this->parseIdentifier($expr->name);
$rtFunc = $method;
$rtClass = $class;
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ',
$class . '::' . $method . '()'
);
if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment(
'Static Method Call: ',
$class . '::' . $method . '()'
);
}
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)];

@ -45,7 +45,9 @@ trait NullsafeAccessTrait
$list = [];
$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) {
if ($expr instanceof Expr\NullsafePropertyFetch) {
@ -83,7 +85,7 @@ trait NullsafeAccessTrait
$last = array_key_last($list);
$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) {
$tmpVar = $this->addTmpVar($key !== $last ? Type::OBJECT : Type::VAR);

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

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

@ -66,10 +66,8 @@ trait DeclarationSymbolTrait
return Type::VAR;
}
protected function parseUse(Node\Stmt\Use_ $v2): string
protected function parseUse(Node\Stmt\Use_ $v2): void
{
$code = '';
foreach ($v2->uses as $use) {
$id = $this->parseIdentifier($use->name);
$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

@ -532,7 +532,7 @@ class Translator extends Preprocessor
$this->formatCppCode($file);
}
public function convertFile(string $file): string
public function convertFile(string $file): ?string
{
$previousPhase = $this->enterCompilerPhase(self::PHASE_CONVERT);
try {
@ -543,11 +543,15 @@ class Translator extends Preprocessor
try {
$cppCode = $this->doConvert($phpCode);
$cppFile = $this->getCppFile($file);
$this->save($cppCode, $cppFile);
if ($cppCode === '') {
$this->removeEmptyTranslationUnitArtifacts($cppFile);
} else {
$this->save($cppCode, $cppFile);
}
$this->phpSrcFiles[] = $file;
// 生成 stub 文件,依赖 convert 阶段的 use 等信息
$this->genStubFile($this->file);
return $cppFile;
return $cppCode === '' ? null : $cppFile;
} catch (Redo $e) {
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
{
return implode(', ', $this->getRegisterClassFunctionCeList($classDef));
@ -2557,7 +2575,7 @@ CODE;
$cppCode .= $this->parseClass($v);
break;
case 'Stmt_Use':
$cppCode .= $this->parseUse($v) . PHP_EOL;
$this->parseUse($v);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v);
@ -2601,6 +2619,15 @@ CODE;
foreach ($this->constData as $name => $data) {
$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;
return $this->genIncludeHeaderFiles() . $constDataCode . $cppCode;
@ -2741,7 +2768,7 @@ CODE;
$code .= $this->parseFunction($v2) . PHP_EOL;
break;
case 'Stmt_Use':
$code .= $this->parseUse($v2) . PHP_EOL;
$this->parseUse($v2);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v2);

Loading…
Cancel
Save