fix(compiler): infer abs result from operand type

master
韩天峰 3 weeks ago
parent 81f298d655
commit cff557b649
  1. 57
      phpunit/code/abs-type-inference.php
  2. 34
      phpunit/src/AbsTypeInferenceTest.php
  3. 58
      src/CompilerBase.php
  4. 6
      src/Optimizer/SsaTypeOptimizer.php

@ -0,0 +1,57 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace {
use function abs as absolute;
function inferredAbsInt(int $value): void
{
$result = abs($value);
var_dump($result);
}
function inferredAliasedAbsInt(int $value): void
{
$result = absolute($value);
var_dump($result);
}
function inferredLocalAbsInt(): void
{
$value = PHP_INT_MIN;
$result = abs($value);
var_dump($result);
}
function inferredAbsFloat(float $value): void
{
$result = abs($value);
var_dump($result);
}
function inferredAbsDynamic(mixed $value): void
{
$result = abs($value);
var_dump($result);
}
}
namespace AbsInferenceNamespace {
function inferredNamespacedAbs(int $value): void
{
// A runtime-provided namespaced function may shadow global abs().
$result = abs($value);
var_dump($result);
}
}
namespace {
function main(): void
{
}
}

@ -0,0 +1,34 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
use TypePhp\CompilerTest;
/**
* @internal
* @coversNothing
*/
final class AbsTypeInferenceTest extends BaseTest
{
public function testResultStorageFollowsTheStaticallySelectedOverload(): void
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/abs-type-inference.php';
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$code = file_get_contents($compiler->convertFile($source));
self::assertIsString($code);
self::assertSame(3, substr_count($code, 'php::Int result = 0;'));
self::assertSame(1, substr_count($code, 'php::Float result = 0;'));
self::assertSame(2, substr_count($code, 'php::Var result;'));
self::assertSame(5, substr_count($code, 'php::fn::abs('));
}
}

@ -3507,28 +3507,9 @@ class CompilerBase implements PropertyAccessContext
return Type::VAR;
}
$globalName = ltrim($name, '\\');
// Math function optimization: propagate Big* return types
if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true)
&& !empty($expr->args)) {
$argType = $this->detectTypeOfExpr($expr->args[0]->value);
if (
$argType === Type::BIGINT
&& in_array($name, ['abs', 'pow', 'sqrt'], true)
) {
return Type::BIGINT;
}
if (
$argType === Type::DECIMAL
&& in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true)
) {
return Type::DECIMAL;
}
if (
$argType === Type::BIGFLOAT
&& in_array($name, ['abs', 'sqrt'], true)
) {
return Type::BIGFLOAT;
}
$mathReturnType = $this->detectMathCallReturnType($functionTarget['lower'], $expr);
if ($mathReturnType !== null) {
return $mathReturnType;
}
if (in_array($name, self::STREAM_FUNCTIONS)) {
return Type::STREAM;
@ -3701,6 +3682,39 @@ class CompilerBase implements PropertyAccessContext
return Type::VAR;
}
/**
* Return the result type selected by TypePHP's statically dispatched math
* overloads. Dynamic operands retain the runtime function's union type.
*/
protected function detectMathCallReturnType(string $name, Expr\FuncCall $expr): ?string
{
if ($expr->args === [] || $expr->args[0]->unpack) {
return null;
}
$argType = $this->detectTypeOfExpr($expr->args[0]->value);
if ($name === 'abs' && in_array($argType, [
Type::INT,
Type::FLOAT,
Type::BIGINT,
Type::DECIMAL,
Type::BIGFLOAT,
], true)) {
return $argType;
}
if ($argType === Type::BIGINT && in_array($name, ['pow', 'sqrt'], true)) {
return Type::BIGINT;
}
if ($argType === Type::DECIMAL && in_array($name, ['pow', 'sqrt', 'floor', 'ceil', 'round'], true)) {
return Type::DECIMAL;
}
if ($argType === Type::BIGFLOAT && $name === 'sqrt') {
return Type::BIGFLOAT;
}
return null;
}
protected function genDynamicPropIncDec($var, string $op, bool $isPre): ?string
{
if (!$this->isPropertyFetch($var)) {

@ -332,6 +332,12 @@ trait SsaTypeOptimizer
return $this->detectConstType($expr) === Type::INT;
}
if ($expr instanceof Node\Expr\FuncCall && $expr->name instanceof Node\Name) {
$target = $this->resolveStaticFunctionCallTarget($expr->name);
return $target['definitelyGlobal']
&& $this->detectMathCallReturnType($target['lower'], $expr) === Type::INT;
}
if ($expr instanceof Node\Expr\BitwiseNot) {
return $this->isSafeSsaIntExpr($expr->expr);
}

Loading…
Cancel
Save