diff --git a/phpunit/code/abs-type-inference.php b/phpunit/code/abs-type-inference.php new file mode 100644 index 00000000..b0aaa07c --- /dev/null +++ b/phpunit/code/abs-type-inference.php @@ -0,0 +1,57 @@ +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(')); + } +} diff --git a/src/CompilerBase.php b/src/CompilerBase.php index a9f95fd1..d28dc0b1 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -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)) { diff --git a/src/Optimizer/SsaTypeOptimizer.php b/src/Optimizer/SsaTypeOptimizer.php index 75895b7b..6d8e89b2 100644 --- a/src/Optimizer/SsaTypeOptimizer.php +++ b/src/Optimizer/SsaTypeOptimizer.php @@ -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); }