refactor(constants): implement proper constant initialization scoping and finalization

- Add constant initialization code separation with before/after statement handling
- Implement constant finalization logic to prevent metadata replacement during re-parsing
- Create scoped initialization blocks using do-while constructs for temporary variables
- Add codegenFinalized flag to track constant processing state
- Introduce new test case for global nested array constants initialization
- Add incremental declaration test for constant initializer preservation
- Move constant value parsing to dedicated finalizeGlobalConstantValue method
- Update translator to handle scoped constant initialization code generation
master
韩天峰 4 weeks ago
parent d059cefea6
commit 30b6f43bb7
  1. 25
      phpunit/src/Build/IncrementalDeclarationTest.php
  2. 4
      src/Preprocessor.php
  3. 30
      src/Resolver/DeclarationSymbolTrait.php
  4. 12
      src/Translator.php
  5. 31
      tests/compiler/stdlib/global-nested-array-constants.phpt

@ -137,6 +137,31 @@ PHP);
); );
} }
public function testGlobalArrayConstantInitializersSurviveColdAndWarmConversion(): void
{
file_put_contents($this->provider, <<<'PHP'
<?php
namespace Incremental;
const LIMIT = 42;
const FIRST = [[[LIMIT, 1]], [[2, 3]]];
const ENABLED = true;
const SECOND = [0 => ['left' => LIMIT], 'tail' => [2, [3]]];
function answer(): int { return FIRST[0][0][0] + SECOND[0]['left']; }
PHP);
$first = $this->convertProject();
$extension = $this->buildDirectory . '/extension-incremental.cc';
$coldCode = file_get_contents($extension);
self::assertStringContainsString('php::Array tmp_var_', $coldCode);
$coldConstants = $this->property($first, 'constants');
self::assertSame(\TypePhp\Type::BOOL, $coldConstants['_const_var_Incremental__ENABLED']->type);
$second = $this->convertProject();
self::assertFalse($this->invoke($second, 'shouldRegeneratePhpFile', $this->provider));
self::assertSame($coldCode, file_get_contents($extension));
$warmConstants = $this->property($second, 'constants');
self::assertSame($coldConstants['_const_var_Incremental__LIMIT']->type, $warmConstants['_const_var_Incremental__LIMIT']->type);
self::assertSame(\TypePhp\Type::BOOL, $warmConstants['_const_var_Incremental__ENABLED']->type);
}
public function testGeneratorFingerprintChangeKeepsIdenticalCppTimestamps(): void public function testGeneratorFingerprintChangeKeepsIdenticalCppTimestamps(): void
{ {
$first = $this->convertProject(); $first = $this->convertProject();

@ -917,9 +917,7 @@ class Preprocessor extends CompilerBase
if (!isset($this->constants[$key])) { if (!isset($this->constants[$key])) {
continue; continue;
} }
$this->resetFunction(); $this->finalizeGlobalConstantValue($this->constants[$key], $constant->value);
$this->constants[$key]->value = $this->parseIdentifier($constant->value);
$this->constants[$key]->codegenFinalized = true;
} }
} }

@ -17,16 +17,36 @@ trait DeclarationSymbolTrait
{ {
foreach ($v2->consts as $const) { foreach ($v2->consts as $const) {
$name = $this->parseIdentifier($const->name); $name = $this->parseIdentifier($const->name);
$value = $this->compilerPhase === self::PHASE_CONVERT
? $this->parseIdentifier($const->value)
: '';
if ($this->namespace) { if ($this->namespace) {
$name = $this->namespace . '\\' . $name; $name = $this->namespace . '\\' . $name;
} }
$this->addConstant($name, $value, $const->value); // Declaration finalization owns whole-program initialization.
// Re-parsing a dirty file must not replace that metadata with
// temporaries allocated in the function-body context.
if ($this->compilerPhase === self::PHASE_CONVERT
&& ($this->constants[$this->escapeConstVar($name)]->codegenFinalized ?? false)) {
continue;
}
$this->addConstant($name, '', $const->value);
if ($this->compilerPhase === self::PHASE_CONVERT) {
$this->finalizeGlobalConstantValue($this->constants[$this->escapeConstVar($name)], $const->value);
}
} }
} }
protected function finalizeGlobalConstantValue(\stdClass $constant, Node\Expr $expression): void
{
$this->resetFunction();
$constant->value = $this->parseIdentifier($expression);
$constant->initializationCode = $this->context->localVars ? $this->genScopeVarDecl() : '';
$constant->initializationCode .= $this->parseBeforeStmtLines();
$constant->afterInitializationCode = $this->parseAfterStmtLines();
// Clean files are not re-parsed by convert(): finalize their scalar
// storage types here too, keeping cold/warm declarations identical.
$constant->type = $this->detectStrValueType($constant->value);
$constant->codegenFinalized = true;
}
protected function addConstant(string $name, string $value, ?Node\Expr $valueExpr = null): void protected function addConstant(string $name, string $value, ?Node\Expr $valueExpr = null): void
{ {
$constInfo = new \stdClass(); $constInfo = new \stdClass();
@ -39,6 +59,8 @@ trait DeclarationSymbolTrait
$constInfo->namespace = $this->namespace; $constInfo->namespace = $this->namespace;
$constInfo->name = $name; $constInfo->name = $name;
$constInfo->sourceFile = $this->file; $constInfo->sourceFile = $this->file;
$constInfo->initializationCode = '';
$constInfo->afterInitializationCode = '';
$this->constants[$this->escapeConstVar($name)] = $constInfo; $this->constants[$this->escapeConstVar($name)] = $constInfo;
$this->symbolDeclInFile[$this->getConstantDependencySymbol($name)] = $this->file; $this->symbolDeclInFile[$this->getConstantDependencySymbol($name)] = $this->file;
} }

@ -1571,8 +1571,20 @@ CODE;
$code .= $this->genCompiledGeneratorFingerprintRegistration(); $code .= $this->genCompiledGeneratorFingerprintRegistration();
$code .= '// register constants' . PHP_EOL; $code .= '// register constants' . PHP_EOL;
foreach ($this->constants as $name => $const) { foreach ($this->constants as $name => $const) {
$initializationCode = $const->initializationCode ?? '';
$afterInitializationCode = $const->afterInitializationCode ?? '';
$scopedInitialization = $initializationCode !== '' || $afterInitializationCode !== '';
if ($scopedInitialization) {
// Each constant has its own temporary namespace. Declaration
// lowering resets temporary IDs between independent values.
$code .= "do {\n" . $initializationCode;
}
$code .= "{$name} = {$const->value};\n"; $code .= "{$name} = {$const->value};\n";
$code .= $afterInitializationCode;
$code .= 'php::fn::define(' . $this->genCharPtr($const->name, true) . ', ' . $name . ');' . PHP_EOL; $code .= 'php::fn::define(' . $this->genCharPtr($const->name, true) . ', ' . $name . ');' . PHP_EOL;
if ($scopedInitialization) {
$code .= "} while (0);\n";
}
} }
$code .= '// global vars ' . PHP_EOL; $code .= '// global vars ' . PHP_EOL;
foreach ($extensionGlobalVars as $name => $type) { foreach ($extensionGlobalVars as $name => $type) {

@ -0,0 +1,31 @@
--TEST--
Global nested array constants keep initialization temporaries in separate scopes
--FILE--
<?php
namespace GlobalArrayConstants {
const LIMIT = 42;
const FIRST = [[[LIMIT, 1]], [[2, 3]]];
const SECOND = [0 => ['left' => LIMIT], 'tail' => [2, [3]]];
const THIRD = [FIRST, SECOND];
}
namespace {
function main(): void
{
var_dump(GlobalArrayConstants\FIRST[0][0][0]);
var_dump(GlobalArrayConstants\FIRST[1][0][1]);
var_dump(GlobalArrayConstants\SECOND[0]['left']);
var_dump(GlobalArrayConstants\SECOND['tail'][1][0]);
var_dump(GlobalArrayConstants\THIRD[0] === GlobalArrayConstants\FIRST);
var_dump(GlobalArrayConstants\THIRD[1] === GlobalArrayConstants\SECOND);
}
}
?>
--EXPECT--
int(42)
int(3)
int(42)
int(3)
bool(true)
bool(true)
Loading…
Cancel
Save