fix(compiler): apply unary plus numeric conversion (#132)
* fix(compiler): apply unary plus numeric conversion * fix(compiler): infer unary plus assignments from target type * fix(compiler): retain unary plus inference for fresh locals * fix(compiler): resolve existing unary assignment locals by compiled namemaster
parent
4f61dda6c3
commit
c54b590ca2
9 changed files with 276 additions and 3 deletions
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
function positiveInteger(int $number): int |
||||||
|
{ |
||||||
|
return +$number; |
||||||
|
} |
||||||
|
|
||||||
|
function positiveFloat(float $number): float |
||||||
|
{ |
||||||
|
return +$number; |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use TypePhp\CompilerTest; |
||||||
|
|
||||||
|
final class UnaryPlusCodegenTest extends \BaseTest |
||||||
|
{ |
||||||
|
public function testNativeNumericUnaryPlusDoesNotBoxItsOperand(): void |
||||||
|
{ |
||||||
|
global $translator; |
||||||
|
|
||||||
|
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||||
|
$translator = $compiler; |
||||||
|
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/unary-plus-codegen.php'; |
||||||
|
$compiler->addFiles([$source]); |
||||||
|
$compiler->prepareFile($source); |
||||||
|
$generated = $compiler->convertFile($source); |
||||||
|
$code = file_get_contents($generated); |
||||||
|
|
||||||
|
self::assertIsString($code); |
||||||
|
self::assertStringNotContainsString('php::Variant(number)', $code); |
||||||
|
self::assertStringContainsString('php::toInt(number)', $code); |
||||||
|
self::assertStringContainsString('php::toFloat(number)', $code); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
--TEST-- |
||||||
|
Unary plus uses the converted assignment result for typed properties |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class UnaryAssignmentBox { |
||||||
|
public float $number = 0.0; |
||||||
|
public ?float $optional = null; |
||||||
|
public static float $shared = 0.0; |
||||||
|
public static ?float $nullableShared = null; |
||||||
|
} |
||||||
|
function assignedOnce(int &$calls): int { ++$calls; return 2; } |
||||||
|
function existingTarget(float $int): int|float { return +($int = 2); } |
||||||
|
function main() { |
||||||
|
$box = new UnaryAssignmentBox(); |
||||||
|
var_dump(+($box->number = 2), $box->number); |
||||||
|
var_dump(+(UnaryAssignmentBox::$shared = 3), UnaryAssignmentBox::$shared); |
||||||
|
var_dump(+($box->optional = 4), +($box->optional = null)); |
||||||
|
var_dump(+(UnaryAssignmentBox::$nullableShared = 5), +(UnaryAssignmentBox::$nullableShared = null)); |
||||||
|
var_dump(+@($box->number = 6)); |
||||||
|
$calls = 0; |
||||||
|
var_dump(+($box->number = assignedOnce($calls)), $calls); |
||||||
|
var_dump(+($local = 7), $local); |
||||||
|
var_dump(existingTarget(0.0)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
float(2) |
||||||
|
float(2) |
||||||
|
float(3) |
||||||
|
float(3) |
||||||
|
float(4) |
||||||
|
int(0) |
||||||
|
float(5) |
||||||
|
int(0) |
||||||
|
float(6) |
||||||
|
float(2) |
||||||
|
int(1) |
||||||
|
int(7) |
||||||
|
int(7) |
||||||
|
float(2) |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
--TEST-- |
||||||
|
Unary plus preserves PHP numeric conversion, errors and evaluation count |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function positive(mixed $value): int|float { return +$value; } |
||||||
|
function numericText(string $value): int|float { return +$value; } |
||||||
|
function truth(bool $value): int { return +$value; } |
||||||
|
function once(int &$calls): string { ++$calls; return '2.5'; } |
||||||
|
function main() { |
||||||
|
$integer = '42'; |
||||||
|
$decimal = '1.5'; |
||||||
|
$boolean = true; |
||||||
|
var_dump(+$integer, +$decimal, +$boolean); |
||||||
|
$assigned = +$decimal; |
||||||
|
var_dump($assigned, numericText('24'), truth(false)); |
||||||
|
foreach (['12', '1.25', '1e2', '9223372036854775808', true, false, null, 7, -0.0] as $value) { |
||||||
|
var_dump(positive($value)); |
||||||
|
} |
||||||
|
foreach (['invalid', [], new stdClass()] as $value) { |
||||||
|
try { positive($value); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } |
||||||
|
} |
||||||
|
set_error_handler(function ($level, $message, $file, $line) { echo $message, "\n"; return true; }); |
||||||
|
var_dump(numericText('12tail')); |
||||||
|
restore_error_handler(); |
||||||
|
$calls = 0; |
||||||
|
var_dump(+once($calls), $calls); |
||||||
|
$source = '8'; |
||||||
|
$alias =& $source; |
||||||
|
$result = +$alias; |
||||||
|
var_dump($result, $source, $alias); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(42) |
||||||
|
float(1.5) |
||||||
|
int(1) |
||||||
|
float(1.5) |
||||||
|
int(24) |
||||||
|
int(0) |
||||||
|
int(12) |
||||||
|
float(1.25) |
||||||
|
float(100) |
||||||
|
float(9.223372036854776E+18) |
||||||
|
int(1) |
||||||
|
int(0) |
||||||
|
int(0) |
||||||
|
int(7) |
||||||
|
float(-0) |
||||||
|
Unsupported operand types: string * int |
||||||
|
Unsupported operand types: array * int |
||||||
|
Unsupported operand types: stdClass * int |
||||||
|
A non-numeric value encountered |
||||||
|
int(12) |
||||||
|
float(2.5) |
||||||
|
int(1) |
||||||
|
int(8) |
||||||
|
string(1) "8" |
||||||
|
string(1) "8" |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
--TEST-- |
||||||
|
Unary plus property and parameter defaults use the converted numeric type |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class PositiveDefaults { |
||||||
|
public int $text = +'12'; |
||||||
|
public int $truth = +true; |
||||||
|
public int $nothing = +null; |
||||||
|
public float $decimal = +'1.5'; |
||||||
|
public int $minimum = +'-9223372036854775808'; |
||||||
|
public ?bool $flag = null; |
||||||
|
public ?int $optionalInt = null; |
||||||
|
public ?float $optionalFloat = null; |
||||||
|
public static ?float $optionalStatic = null; |
||||||
|
public const VALUE = +'24'; |
||||||
|
} |
||||||
|
function defaultNumber(int $value = +'6'): int { return $value; } |
||||||
|
function main() { |
||||||
|
$value = new PositiveDefaults(); |
||||||
|
var_dump($value->text, $value->truth, $value->nothing, $value->decimal); |
||||||
|
var_dump(PositiveDefaults::VALUE, defaultNumber()); |
||||||
|
var_dump($value->minimum === PHP_INT_MIN); |
||||||
|
var_dump(+$value->flag); |
||||||
|
$value->flag = true; |
||||||
|
var_dump(+$value->flag); |
||||||
|
var_dump(+$value->optionalInt, +$value->optionalFloat, +PositiveDefaults::$optionalStatic); |
||||||
|
var_dump(+(@$value->optionalFloat)); |
||||||
|
var_dump(+($value->flag ? $value->optionalFloat : PositiveDefaults::$optionalStatic)); |
||||||
|
$value->optionalFloat = 1.5; |
||||||
|
var_dump(+$value->optionalFloat); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(12) |
||||||
|
int(1) |
||||||
|
int(0) |
||||||
|
float(1.5) |
||||||
|
int(24) |
||||||
|
int(6) |
||||||
|
bool(true) |
||||||
|
int(0) |
||||||
|
int(1) |
||||||
|
int(0) |
||||||
|
int(0) |
||||||
|
int(0) |
||||||
|
int(0) |
||||||
|
int(0) |
||||||
|
float(1.5) |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
--TEST-- |
||||||
|
Unary plus preserves arbitrary precision values assigned to fresh locals |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function main() { |
||||||
|
echo (+($integer = std::bigInt(2)))->toString(), "\n"; |
||||||
|
echo (+($floating = std::bigFloat(2.5)))->toString(), "\n"; |
||||||
|
echo (+($decimal = std::decimal('2.5')))->toString(), "\n"; |
||||||
|
var_dump(+($native = 7), +($fraction = 1.5)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
2 |
||||||
|
2.5 |
||||||
|
2.5 |
||||||
|
int(7) |
||||||
|
float(1.5) |
||||||
Loading…
Reference in new issue