From 9fbb2a3f0500f68bae73085fcf2add3bac133207 Mon Sep 17 00:00:00 2001 From: PurHur Date: Sun, 17 May 2026 08:33:19 +0000 Subject: [PATCH] Stdlib: floatval, boolval, gettype(null) and expanded PHPT specs Add floatval and boolval builtins with VM and JIT paths for supported scalar types. Extend gettype and is_* JIT handling for null and boolean operands. Add compliance and integration PHPT coverage. Co-authored-by: Cursor --- ext/standard/Module.php | 2 + ext/standard/boolval.php | 110 ++++++++++++++++++ ext/standard/floatval.php | 67 +++++++++++ ext/standard/gettype.php | 3 + ext/types/is_type.php | 4 + test/compliance/cases/stdlib/boolval.phpt | 25 ++++ test/compliance/cases/stdlib/floatval.phpt | 13 +++ .../compliance/cases/stdlib/gettype_null.phpt | 7 ++ test/real/cases/stdlib_casts.phpt | 13 +++ test/real/cases/stdlib_digest.phpt | 6 + 10 files changed, 250 insertions(+) create mode 100644 ext/standard/boolval.php create mode 100644 ext/standard/floatval.php create mode 100644 test/compliance/cases/stdlib/boolval.phpt create mode 100644 test/compliance/cases/stdlib/floatval.phpt create mode 100644 test/compliance/cases/stdlib/gettype_null.phpt create mode 100644 test/real/cases/stdlib_casts.phpt diff --git a/ext/standard/Module.php b/ext/standard/Module.php index e95b9ca0b83..129bc3d838d 100755 --- a/ext/standard/Module.php +++ b/ext/standard/Module.php @@ -26,6 +26,8 @@ public function getFunctions(): array new round(), new sqrt(), new intval(), + new floatval(), + new boolval(), new gettype(), new int_min(), new int_max(), diff --git a/ext/standard/boolval.php b/ext/standard/boolval.php new file mode 100644 index 00000000000..77e8cf1902f --- /dev/null +++ b/ext/standard/boolval.php @@ -0,0 +1,110 @@ +calledArgs)) { + throw new \LogicException('boolval() requires exactly one argument'); + } + $v = $frame->calledArgs[0]->resolveIndirect(); + if (null === $frame->returnVar) { + return; + } + $frame->returnVar->bool(self::isTruthy($v)); + } + + public Context $context; + + public function call(Context $context, JITVariable ...$args): Value + { + $this->context = $context; + if (1 !== count($args)) { + throw new \LogicException('boolval() requires exactly one argument'); + } + switch ($args[0]->type) { + case JITVariable::TYPE_NATIVE_LONG: + $v = $context->helper->loadValue($args[0]); + $zero = $v->typeOf()->constInt(0, false); + + return $context->builder->icmp(Builder::INT_NE, $v, $zero); + case JITVariable::TYPE_NATIVE_DOUBLE: + $v = $context->helper->loadValue($args[0]); + $zero = $v->typeOf()->constReal(0.0); + + return $context->builder->fcmp(Builder::REAL_ONE, $v, $zero); + case JITVariable::TYPE_NATIVE_BOOL: + return $context->helper->loadValue($args[0]); + case JITVariable::TYPE_STRING: + return $this->stringTruthy($context, $context->helper->loadValue($args[0])); + case JITVariable::TYPE_NULL: + return $context->constantFromBool(false); + default: + throw new \LogicException('boolval() does not support this value type in this compiler build'); + } + } + + private static function isTruthy(Variable $v): bool + { + switch ($v->type) { + case Variable::TYPE_NULL: + return false; + case Variable::TYPE_INTEGER: + return 0 !== $v->toInt(); + case Variable::TYPE_FLOAT: + return 0.0 !== $v->toFloat(); + case Variable::TYPE_BOOLEAN: + return $v->toBool(); + case Variable::TYPE_STRING: + $s = $v->toString(); + + return '' !== $s && '0' !== $s; + default: + throw new \LogicException('boolval() does not support this value type in this compiler build'); + } + } + + private function stringTruthy(Context $context, Value $strPtr): Value + { + $structName = $strPtr->typeOf()->getElementType()->getName(); + $map = $context->structFieldMap[$structName]; + $len = $context->builder->load( + $context->builder->structGep($strPtr, $map['length']) + ); + $zero = $len->typeOf()->constInt(0, false); + $isEmpty = $context->builder->icmp(Builder::INT_EQ, $len, $zero); + $one = $len->typeOf()->constInt(1, false); + $isOne = $context->builder->icmp(Builder::INT_EQ, $len, $one); + $ch = $context->builder->load( + $context->builder->structGep($strPtr, $map['value']) + ); + $charZero = $ch->typeOf()->constInt(ord('0'), false); + $isCharZero = $context->builder->icmp(Builder::INT_EQ, $ch, $charZero); + $onlyZero = $context->builder->and($isOne, $isCharZero); + $falsy = $context->builder->or($isEmpty, $onlyZero); + + return $context->builder->not($falsy); + } +} diff --git a/ext/standard/floatval.php b/ext/standard/floatval.php new file mode 100644 index 00000000000..07bb082f851 --- /dev/null +++ b/ext/standard/floatval.php @@ -0,0 +1,67 @@ +calledArgs)) { + throw new \LogicException('floatval() requires exactly one argument'); + } + $v = $frame->calledArgs[0]->resolveIndirect(); + if (null === $frame->returnVar) { + return; + } + if (Variable::TYPE_INTEGER === $v->type) { + $frame->returnVar->float((float) $v->toInt()); + + return; + } + if (Variable::TYPE_FLOAT === $v->type) { + $frame->returnVar->float($v->toFloat()); + + return; + } + throw new \LogicException('floatval() only supports integers and floats in this compiler build'); + } + + public Context $context; + + public function call(Context $context, JITVariable ...$args): Value + { + $this->context = $context; + if (1 !== count($args)) { + throw new \LogicException('floatval() requires exactly one argument'); + } + $v = $context->helper->loadValue($args[0]); + $double = $context->getTypeFromString('double'); + switch ($args[0]->type) { + case JITVariable::TYPE_NATIVE_LONG: + return $context->builder->siToFp($v, $double); + case JITVariable::TYPE_NATIVE_DOUBLE: + return $v; + default: + throw new \LogicException('floatval() only supports integers and floats in this compiler build'); + } + } +} diff --git a/ext/standard/gettype.php b/ext/standard/gettype.php index e00ba073c9a..f468797d2fb 100644 --- a/ext/standard/gettype.php +++ b/ext/standard/gettype.php @@ -67,6 +67,9 @@ public function call(Context $context, JITVariable ...$args): Value case JITVariable::TYPE_STRING: $label = 'string'; break; + case JITVariable::TYPE_NULL: + $label = 'NULL'; + break; default: throw new \LogicException('gettype() does not support this value type in this compiler build'); } diff --git a/ext/types/is_type.php b/ext/types/is_type.php index 3ccafc69b88..e57f5c3cde1 100755 --- a/ext/types/is_type.php +++ b/ext/types/is_type.php @@ -48,8 +48,12 @@ public function call(Context $context, JITVariable ... $args): Value { return $this->context->constantFromBool($this->type === Variable::TYPE_INTEGER); case JITVariable::TYPE_NATIVE_DOUBLE: return $this->context->constantFromBool($this->type === Variable::TYPE_FLOAT); + case JITVariable::TYPE_NATIVE_BOOL: + return $this->context->constantFromBool($this->type === Variable::TYPE_BOOLEAN); case JITVariable::TYPE_STRING: return $this->context->constantFromBool($this->type === Variable::TYPE_STRING); + case JITVariable::TYPE_NULL: + return $this->context->constantFromBool($this->type === Variable::TYPE_NULL); default: throw new \LogicException('Non-implemented type handled for ' . $this->name . '(): ' . JITVariable::getStringType($args[0]->type)); } diff --git a/test/compliance/cases/stdlib/boolval.phpt b/test/compliance/cases/stdlib/boolval.phpt new file mode 100644 index 00000000000..49343bb5bef --- /dev/null +++ b/test/compliance/cases/stdlib/boolval.phpt @@ -0,0 +1,25 @@ +--TEST-- +stdlib boolval() for scalar values +--FILE-- +