- Updated swoole/phpx from ~2.6.7 to ~2.6.8 in composer.json
- Bumped dependency version to latest stable release
- Maintained compatibility with existing codebase
- Translate Chinese comments to English in dump-ast.php
- Update comment headers from Chinese to English in extractor.php
- Replace Chinese documentation comments with English equivalents
- Maintain all functionality while improving internationalization
- Standardize commenting style across CLI utility files
Consolidate float-to-C++ literal generation into Utils::genFloatLiteral().
Previously:
- genCValue() stringified floats directly via (string), emitting 'INF', '-INF', 'NAN', or losing the floating-point decimal point for whole numbers like 1.0 -> '1'.
- BinaryOpTrait::genFloatLiteral() used sprintf('%.17g') without handling INF or NAN.
Now all float code generation paths delegate to Utils::genFloatLiteral(), mapping INF/-INF/NAN to std::numeric_limits<double> and ensuring whole numbers retain .0.
doFoldKnownClass folds class_exists() whenever the name is a literal the
symbol table knows. That table also holds traits, so a trait name folded
to true while PHP answers false:
trait Helper {}
class_exists('Helper'); // folded to true
$name = 'Helper';
class_exists($name); // reaches php::fn::class_exists, answers false
The same program therefore gives two different answers for the same
trait, decided only by whether the argument is a literal.
The runtime side is already right, and deliberately so: traits are
compile-time AST templates in TypePHP, which is why
tests/compiler/stdlib/class_exists.phpt expects trait_exists() to be
false. Only the constant fold disagreed - with PHP and with the
compiler's own runtime.
A trait name now folds to false. Classes and enums keep folding to true,
which matches PHP: an enum is a class, a trait is not.
class_exists.phpt gains the literal and non-literal trait cases, and
ClassExistsTraitFoldTest pins the fold decision in the generated C++.
parseUnaryMinus emitted '-' . $code without guarding against an operand
that itself starts with '-', pasting into the C++ pre-decrement token:
`- -$x` compiled to `--x`. On a php::Var operand the generated
translation unit fails to build ("expression is not assignable"); on a
native int operand it builds and silently decrements: a function
`(int $x) => - -$x` returned 8 for input 9.
Parenthesize the operand exactly when its emitted code starts with '-'
(a nested unary minus or a negative literal), so plain literals keep
their compact form (`-7L`). Binary operands are already self-wrapped in
parentheses, and unary plus needs no change since it returns the
operand unchanged.
parseUse rebuilt the imported constant name by splitting on the last
backslash, but strrpos() returns false for a single-segment name:
`use const PHP_EOL;` registered the import as `\HP_EOL`
(substr($id, false + 1) drops the first character), and the compiled
program failed at runtime with `Undefined constant "HP_EOL"`.
$id is already the fully qualified constant name, so store it directly.
For multi-segment imports the removed recomposition produced the same
value, so their behavior is unchanged.
doFoldSsaType folded a statically type-known is_int()/is_float()/
is_bool() call to the literal `true`, discarding the argument entirely.
With `function f(): int`, `if (is_int(f()))` compiled to `if (true)`
and f() was never invoked — its side effects silently vanished.
Fold to a bare `true` only for plain variables and scalar literals;
for any other argument emit `((void)(expr), true)` so the operand is
still evaluated, mirroring how genIsNull already handles native scalar
operands.
The flag checks that translate `break N` / `continue N` were emitted only
at the end of each enclosing loop body. After the inner construct exited
with the countdown flag set, every trailing statement of the enclosing
body still executed before the check ran:
foreach ([1] as $x) {
foreach ([1] as $y) { break 2; }
echo "leaked"; // ran in compiled output, not in PHP
}
The native (int-typed) switch path was worse: its check sat inside the
do-while(0) wrapper, decrementing the flag a second time for the switch
level the C++ `break` had already exited. A `break 2` from a native
switch inside a loop therefore never exited the loop at all.
Emit the propagation check immediately after every nested loop / switch
statement instead, from the statement dispatcher, and drop the dead
end-of-body emissions. The check now also distinguishes the enclosing
construct: when it sits inside a switch, a continue that lands on the
switch level lowers to `break`, matching PHP's continue-targets-switch
semantics.
parseBreak/parseContinue now reject levels exceeding the number of
enclosing breakable constructs - the same compile-time validation PHP
performs (`Cannot 'break' 2 levels`) - which the countdown scheme
relies on to terminate at an enclosing construct.
The continue-2-while scenario in break-continue-level.phpt encoded the
old leaked behavior: its `$i++` after the inner loop only ran because of
the misplaced check; standard PHP loops forever on it. The counter now
advances before the inner loop.
- Updated swoole/phpx from ~2.6.6 to ~2.6.7 in composer.json
- Bumped minor version for bug fixes and improvements
- Maintained compatibility with existing codebase
- Updated dependency constraint in require section
- Updated swoole/phpx from ~2.6.6 to ~2.6.7 in composer.json
- Bumped minor version for bug fixes and improvements
- Maintained compatibility with existing codebase
- Updated dependency constraint in require section
- Updated swoole/phpx dependency from ~2.6.4 to ~2.6.6
- Bumped project version from 0.6.6 to 0.6.7 in project.yml
- Updated file version from 0.6.6.1112 to 0.6.7.1112 in project.yml
- Updated product version from 0.6.6 to 0.6.7 in project.yml
- Updated VERSION constant from '0.6.6' to '0.6.7' in Translator.php
The flag checks that translate `break N` / `continue N` were emitted only
at the end of each enclosing loop body. After the inner construct exited
with the countdown flag set, every trailing statement of the enclosing
body still executed before the check ran:
foreach ([1] as $x) {
foreach ([1] as $y) { break 2; }
echo "leaked"; // ran in compiled output, not in PHP
}
The native (int-typed) switch path was worse: its check sat inside the
do-while(0) wrapper, decrementing the flag a second time for the switch
level the C++ `break` had already exited. A `break 2` from a native
switch inside a loop therefore never exited the loop at all.
Emit the propagation check immediately after every nested loop / switch
statement instead, from the statement dispatcher, and drop the dead
end-of-body emissions. The check now also distinguishes the enclosing
construct: when it sits inside a switch, a continue that lands on the
switch level lowers to `break`, matching PHP's continue-targets-switch
semantics.
parseBreak/parseContinue now reject levels exceeding the number of
enclosing breakable constructs - the same compile-time validation PHP
performs (`Cannot 'break' 2 levels`) - which the countdown scheme
relies on to terminate at an enclosing construct.
The continue-2-while scenario in break-continue-level.phpt encoded the
old leaked behavior: its `$i++` after the inner loop only ran because of
the misplaced check; standard PHP loops forever on it. The counter now
advances before the inner loop.
- Updated swoole/phpx dependency from ~2.6.4 to ~2.6.6
- Bumped project version from 0.6.6 to 0.6.7 in project.yml
- Updated file version from 0.6.6.1112 to 0.6.7.1112 in project.yml
- Updated product version from 0.6.6 to 0.6.7 in project.yml
- Updated VERSION constant from '0.6.6' to '0.6.7' in Translator.php