fix(generator): normalize float literal emission and handle INF/NAN constants

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.
master
pratik bhujel 17 hours ago
parent b493ac79c5
commit 2d81626a84
  1. 19
      phpunit/code/float-declaration-metadata.php
  2. 13
      phpunit/code/float-literal-special.php
  3. 4
      phpunit/src/CompilerBaseApiTest.php
  4. 3
      phpunit/src/Generator/UtilsTest.php
  5. 77
      phpunit/src/OperatorTest.php
  6. 14
      src/CompilerBase.php
  7. 18
      src/Generator/Utils.php
  8. 9
      src/Parser/BinaryOpTrait.php
  9. 8
      src/Parser/ConstantExpressionTrait.php
  10. 4
      src/gen_stub.php

@ -0,0 +1,19 @@
<?php
class FloatDeclarationContainer
{
public const float POSITIVE_INF = INF;
public const float NEGATIVE_INF = -INF;
public const float NOT_A_NUMBER = NAN;
public const float CONST_E = M_E;
public const float CONST_ONE_POINT_FIVE = 1.5;
public float $property_e = M_E;
public float $property_inf = INF;
public float $property_nan = NAN;
public float $property_one_point_five = 1.5;
}
function main(): void
{
}

@ -0,0 +1,13 @@
<?php
function get_special_floats(): array
{
return [
1.0,
0.0,
INF,
-INF,
NAN,
M_E,
];
}

@ -361,7 +361,7 @@ PHP);
public function testGeneratedCValuesAreAlwaysSourceCodeStrings(): void
{
$this->assertSame((string) M_E, $this->invokeMethod('genCValue', M_E));
$this->assertSame(M_E, (float) $this->invokeMethod('genCValue', M_E));
$this->assertSame('1', $this->invokeMethod('genCValue', true));
$this->assertSame('0', $this->invokeMethod('genCValue', false));
@ -370,7 +370,7 @@ PHP);
new \PhpParser\Node\Expr\ConstFetch(new \PhpParser\Node\Name('M_E'))
);
$this->assertIsString($code);
$this->assertSame((string) M_E, $code);
$this->assertSame(M_E, (float) $code);
}
public function testNumericStringIdentifiersGenerateSourceCodeStrings(): void

@ -49,7 +49,8 @@ class UtilsTest extends TestCase
public function testGenCValueFloat(): void
{
$result = $this->invokeMethod('genCValue', 3.14);
$this->assertSame((string) 3.14, $result);
$this->assertSame(3.14, (float) $result);
$this->assertMatchesRegularExpression('/[.E]/i', $result);
}
public function testGenCValueBool(): void

@ -85,4 +85,81 @@ class OperatorTest extends \BaseTest
{
$this->exec('Cannot divide or modulo by zero', 'assign-modulo-by-zero.php');
}
public function testFloatLiteralSpecialValuesAndWholeNumbers(): void
{
$previous = ini_set('precision', '14');
try {
global $translator;
$compiler = \TypePhp\CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$testFile = __DIR__ . '/../code/float-literal-special.php';
$compiler->addFiles([$testFile]);
$compiler->prepareFile($testFile);
$cppFile = $compiler->convertFile($testFile);
$cpp = file_get_contents($cppFile);
} finally {
if ($previous !== false) {
ini_set('precision', $previous);
}
}
$this->assertStringContainsString('1.0', $cpp);
$this->assertStringContainsString('0.0', $cpp);
$this->assertStringContainsString('std::numeric_limits<double>::infinity()', $cpp);
$this->assertStringContainsString('-std::numeric_limits<double>::infinity()', $cpp);
$this->assertStringContainsString('std::numeric_limits<double>::quiet_NaN()', $cpp);
$this->assertStringContainsString('2.7182818284590451', $cpp);
$this->assertStringNotContainsString('2.718281828459)', $cpp);
}
public function testFloatDeclarationMetadataIgnoresHostPrecisionAndHandlesSpecialValues(): void
{
$previous = ini_set('precision', '14');
try {
global $translator;
$compiler = \TypePhp\CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$testFile = TYPEPHP_ROOT_PATH . '/phpunit/code/float-declaration-metadata.php';
$compiler->addFiles([$testFile]);
$compiler->prepareFile($testFile);
$compiler->convertFile($testFile);
$arginfoHeader = $compiler->getArgInfoHeaderFile($testFile);
$arginfo = file_get_contents($arginfoHeader);
$extension = file_get_contents($compiler->genExtension());
} finally {
if ($previous !== false) {
ini_set('precision', $previous);
}
}
$this->assertStringContainsString('ZVAL_DOUBLE(&const_POSITIVE_INF_value, std::numeric_limits<double>::infinity());', $arginfo);
$this->assertStringContainsString('ZVAL_DOUBLE(&const_NEGATIVE_INF_value, -std::numeric_limits<double>::infinity());', $arginfo);
$this->assertStringContainsString('ZVAL_DOUBLE(&const_NOT_A_NUMBER_value, std::numeric_limits<double>::quiet_NaN());', $arginfo);
$this->assertStringContainsString('ZVAL_DOUBLE(&const_CONST_E_value, 2.7182818284590451);', $arginfo);
$this->assertStringContainsString('ZVAL_DOUBLE(&const_CONST_ONE_POINT_FIVE_value, 1.5);', $arginfo);
$this->assertStringNotContainsString('2.718281828459);', $arginfo);
$this->assertStringContainsString('php::toFloat(2.7182818284590451)', $extension);
$this->assertStringContainsString('php::toFloat(std::numeric_limits<double>::infinity())', $extension);
$this->assertStringContainsString('php::toFloat(std::numeric_limits<double>::quiet_NaN())', $extension);
$this->assertStringNotContainsString('2.718281828459)', $extension);
}
public function testFloatLiteralEmissionIsLocaleIndependent(): void
{
$previousLocale = setlocale(LC_ALL, 'de_DE.UTF-8', 'da_DK.UTF-8', 'en_DK.utf8');
try {
global $translator;
$compiler = \TypePhp\CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$result = $compiler->genFloatLiteral(1.5);
$this->assertSame('1.5', $result);
$this->assertStringNotContainsString(',', $result);
} finally {
if ($previousLocale !== false) {
setlocale(LC_ALL, $previousLocale);
}
}
}
}

@ -395,7 +395,6 @@ class CompilerBase implements PropertyAccessContext
protected array $linkPaths = []; // --link-path / -L: user-specified library search paths
/** @var list<string> Required PHP modules recorded in zend_module_entry.deps. */
protected array $extensionDependencies = [];
protected int $floatPrecision = 17;
protected bool $debug = false;
protected bool $formatCode = false; // --format: enable clang-format (disabled by default)
protected bool $printBacktraceOnError = true;
@ -4246,18 +4245,7 @@ class CompilerBase implements PropertyAccessContext
protected function parseScalarFloat(Node\Scalar\Float_ $expr): string
{
$value = $expr->value;
if (is_nan($value)) {
return self::VALUE_NAN;
}
if (is_infinite($value)) {
return $value > 0 ? self::VALUE_INF : '-' . self::VALUE_INF;
}
if (floor($value) == $value && abs($value) < 1e15) {
return number_format($value, 1, '.', '');
}
return sprintf('%.' . $this->floatPrecision . 'g', $value);
return $this->genFloatLiteral($expr->value);
}
protected function parseIsset(Expr\Isset_ $expr): string

@ -25,13 +25,29 @@ trait Utils
return $value . $this->getPlatform()->getIntegerLiteralSuffix();
}
public function genFloatLiteral(float $value): string
{
if (is_nan($value)) {
return self::VALUE_NAN;
}
if (is_infinite($value)) {
return $value > 0 ? self::VALUE_INF : '-' . self::VALUE_INF;
}
$text = sprintf('%.17h', $value);
// Make sure the literal is parsed as a C++ double.
if (!str_contains($text, '.') && !str_contains(strtolower($text), 'e')) {
$text .= '.0';
}
return $text;
}
protected function genCValue(mixed $value): string
{
if (is_int($value)) {
return $this->genIntegerLiteral($value);
}
if (is_float($value)) {
return (string) $value;
return $this->genFloatLiteral($value);
}
if (is_bool($value)) {
return $value ? '1' : '0';

@ -490,15 +490,6 @@ trait BinaryOpTrait
return '((php::Var(' . $leftExpr . ')) ' . $op . ' (php::Var(' . $rightExpr . ')))';
}
protected function genFloatLiteral(float $value): string
{
$text = sprintf('%.17g', $value);
// Make sure the literal is parsed as a C++ double.
if (!str_contains($text, '.') && !str_contains(strtolower($text), 'e')) {
$text .= '.0';
}
return $text;
}
protected function shouldMaterializeOrderedOperand(NodeAbstract $expr): bool
{

@ -236,13 +236,7 @@ trait ConstantExpressionTrait
return $this->genIntegerLiteral($value);
}
if (is_float($value)) {
if (is_nan($value)) {
return self::VALUE_NAN;
}
if (is_infinite($value)) {
return $value > 0 ? self::VALUE_INF : '-' . self::VALUE_INF;
}
return $this->genCValue($value);
return $this->genFloatLiteral($value);
}
if (is_bool($value)) {
return $value ? 'true' : 'false';

@ -2827,8 +2827,10 @@ class EvaluatedValue
// reduced constant string expressions. Emitting that value avoids
// leaking heredoc/nowdoc source syntax into generated C++.
return '"' . getTranslator()->escapeString((string) $this->value) . '"';
} elseif ($this->type->isInt() or $this->type->isFloat()) {
} elseif ($this->type->isInt()) {
return strval($this->value);
} elseif ($this->type->isFloat()) {
return getTranslator()->genFloatLiteral((float) $this->value);
} elseif ($this->type->isBool()) {
return $this->value ? 'true' : 'false';
}

Loading…
Cancel
Save