diff --git a/src/CompilerBase.php b/src/CompilerBase.php index db84d361..def34fc0 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3472,7 +3472,14 @@ protected function detectTypeOfExpr($expr): string return Type::BOOL; case 'Expr_BitwiseNot': $inner = $this->detectTypeOfExpr($expr->expr); - return $inner === Type::BIGINT ? Type::BIGINT : Type::INT; + return match ($inner) { + Type::BIGINT => Type::BIGINT, + // ~"abc" inverts the bytes of the string. + Type::STR => Type::STR, + // ~bool and ~array throw a TypeError at runtime. + Type::BOOL, Type::ARRAY => Type::VAR, + default => Type::INT, + }; case 'Expr_Print': case 'Expr_Cast_Int': return Type::INT; diff --git a/src/Parser/UnaryExpressionTrait.php b/src/Parser/UnaryExpressionTrait.php old mode 100644 new mode 100755 index 07e6a1ef..a38043ae --- a/src/Parser/UnaryExpressionTrait.php +++ b/src/Parser/UnaryExpressionTrait.php @@ -85,6 +85,15 @@ protected function parseBitwiseNot(Expr\BitwiseNot $expr): string return 'php::BigInt::bitNot(' . $this->parseExpr($expr->expr) . ')'; } $var = $this->parseIdentifier($expr->expr); + // A statically non-numeric operand goes through Zend's + // bitwise_not_function(): a string has its bytes inverted and stays a + // string, while bool and array raise "Cannot perform bitwise not on ...". + if ($type === Type::STR) { + return $this->convertStringExpr('(~php::Variant(' . $var . '))'); + } + if ($type === Type::BOOL || $type === Type::ARRAY) { + return '(~php::Variant(' . $var . '))'; + } return '~' . $this->convertIntExpr($var); } diff --git a/tests/compiler/operator/bitwise-not-operands.phpt b/tests/compiler/operator/bitwise-not-operands.phpt new file mode 100644 index 00000000..52bb9003 --- /dev/null +++ b/tests/compiler/operator/bitwise-not-operands.phpt @@ -0,0 +1,52 @@ +--TEST-- +~ inverts the bytes of a string and raises TypeError for bool, as in PHP +--FILE-- + ', is_string($v) ? 'string:' . bin2hex($v) : var_export($v, true), "\n"; + } catch (\Throwable $e) { + echo $nome, ' => ', get_class($e), ': ', $e->getMessage(), "\n"; + } +} + +function main(): void +{ + ini_set('display_errors', '0'); + t('~false', fn() => notb(false)); + t('~true', fn() => notb(true)); + t('~"abc"', fn() => nots('abc')); + t('~""', fn() => nots('')); + t('~"1e3"', fn() => nots('1e3')); + t('$x = ~"ab"', fn() => notsLocal('ab')); + t('~~"xyz"', fn() => twice('xyz')); + t('~5', fn() => noti(5)); + t('~PHP_INT_MIN', fn() => noti(PHP_INT_MIN)); + t('~1.5', fn() => notf(1.5)); + t('~mixed int', fn() => notm(7)); +} +?> +--EXPECT-- +~false => TypeError: Cannot perform bitwise not on false +~true => TypeError: Cannot perform bitwise not on true +~"abc" => string:9e9d9c +~"" => string: +~"1e3" => string:ce9acc +$x = ~"ab" => string:9e9d +~~"xyz" => string:78797a +~5 => -6 +~PHP_INT_MIN => 9223372036854775807 +~1.5 => -2 +~mixed int => -8