parent
f2c9309857
commit
78135d75ef
12 changed files with 441 additions and 0 deletions
@ -0,0 +1,10 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$a = PHP_INT_MAX + 1; |
||||||
|
$b = PHP_INT_MAX * 2; |
||||||
|
$c = 1 + 2; |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
use native_types; |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(-1 << 2); |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
use native_types; |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(-8 >> 2); |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
use native_types; |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(1 >> -1); |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
use native_types; |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(1 << 3); |
||||||
|
var_dump(5 >> 1); |
||||||
|
var_dump(8 >> 2); |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
use native_types; |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(1 << 64); |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
use native_types; |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(1 << 63); |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$a = 1 << 64; |
||||||
|
$b = 1 >> 64; |
||||||
|
$c = -1 >> 64; |
||||||
|
$d = 1 >> -1; |
||||||
|
$e = 1 << 2; |
||||||
|
$f = 3 >> 1; |
||||||
|
} |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests; |
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
use PhpParser\Node; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
use TypePhp\Diagnostics\DiagnosticReporter; |
||||||
|
use TypePhp\Exception\TestError; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constant integer arithmetic overflow emits compile-time warnings and folds |
||||||
|
* to the PHP float result in non-native mode. |
||||||
|
*/ |
||||||
|
class ConstantArithmeticOverflowTest extends TestCase |
||||||
|
{ |
||||||
|
public function testOverflowingConstantArithmeticEmitsWarning(): void |
||||||
|
{ |
||||||
|
$reporter = $this->compileWithReporter(); |
||||||
|
|
||||||
|
$overflowWarnings = array_values(array_filter( |
||||||
|
$reporter->warnings, |
||||||
|
fn (string $message): bool => str_contains($message, 'Constant integer arithmetic overflows int64') |
||||||
|
)); |
||||||
|
$this->assertCount(2, $overflowWarnings); |
||||||
|
$this->assertStringContainsString('9223372036854775807 + 1', $overflowWarnings[0]); |
||||||
|
$this->assertStringContainsString('folding to PHP float result', $overflowWarnings[0]); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNonOverflowingConstantArithmeticDoesNotWarn(): void |
||||||
|
{ |
||||||
|
$reporter = $this->compileWithReporter(); |
||||||
|
|
||||||
|
foreach ($reporter->warnings as $message) { |
||||||
|
$this->assertStringNotContainsString('1 + 2', $message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return object{warnings: list<string>} |
||||||
|
*/ |
||||||
|
private function compileWithReporter(): object |
||||||
|
{ |
||||||
|
global $translator; |
||||||
|
$compiler = CompilerTest::create(ROOT_PATH); |
||||||
|
$translator = $compiler; |
||||||
|
$reporter = new class implements DiagnosticReporter { |
||||||
|
/** @var list<string> */ |
||||||
|
public array $warnings = []; |
||||||
|
|
||||||
|
public function fatal(string $message): never |
||||||
|
{ |
||||||
|
throw new TestError($message); |
||||||
|
} |
||||||
|
|
||||||
|
public function warning(Node $node, string $file, string $message): void |
||||||
|
{ |
||||||
|
$this->warnings[] = $message; |
||||||
|
} |
||||||
|
}; |
||||||
|
$compiler->setDiagnosticReporter($reporter); |
||||||
|
|
||||||
|
$testFile = __DIR__ . '/../code/constant-overflow-warning.php'; |
||||||
|
$compiler->addFiles([$testFile]); |
||||||
|
$compiler->prepareFile($testFile); |
||||||
|
$compiler->convertFile($testFile); |
||||||
|
|
||||||
|
return $reporter; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,149 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests; |
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
use PhpParser\Node; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
use TypePhp\Diagnostics\DiagnosticReporter; |
||||||
|
use TypePhp\Exception\TestError; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constant bit shift boundaries emit compile-time warnings and keep PHP |
||||||
|
* semantics in non-native mode. |
||||||
|
*/ |
||||||
|
class ShiftBoundaryTest extends TestCase |
||||||
|
{ |
||||||
|
public function testShiftCountAtLeastWordSizeEmitsWarning(): void |
||||||
|
{ |
||||||
|
$reporter = $this->compileWithReporter(); |
||||||
|
|
||||||
|
$overflowWarnings = array_values(array_filter( |
||||||
|
$reporter->warnings, |
||||||
|
fn (string $message): bool => str_contains($message, 'Bit shift count 64 is >= 64') |
||||||
|
)); |
||||||
|
$this->assertCount(3, $overflowWarnings); |
||||||
|
foreach ($overflowWarnings as $message) { |
||||||
|
$this->assertStringContainsString('folding with PHP semantics', $message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function testNegativeShiftCountEmitsWarning(): void |
||||||
|
{ |
||||||
|
$reporter = $this->compileWithReporter(); |
||||||
|
|
||||||
|
$negativeWarnings = array_values(array_filter( |
||||||
|
$reporter->warnings, |
||||||
|
fn (string $message): bool => str_contains($message, 'Bit shift by a negative number') |
||||||
|
)); |
||||||
|
$this->assertCount(1, $negativeWarnings); |
||||||
|
$this->assertStringContainsString('ArithmeticError', $negativeWarnings[0]); |
||||||
|
} |
||||||
|
|
||||||
|
public function testInRangeShiftDoesNotWarn(): void |
||||||
|
{ |
||||||
|
$reporter = $this->compileWithReporter(); |
||||||
|
|
||||||
|
foreach ($reporter->warnings as $message) { |
||||||
|
$this->assertStringNotContainsString('Bit shift count 2 is >=', $message); |
||||||
|
$this->assertStringNotContainsString('Bit shift count 1 is >=', $message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function testNativeModeRejectsShiftCountAtLeastWordSize(): void |
||||||
|
{ |
||||||
|
$this->expectException(TestError::class); |
||||||
|
$this->expectExceptionMessage('Bit shift count 64 is >= 64 and is not supported in native mode'); |
||||||
|
$this->compileNativeWithReporter('shift-boundary-native-overflow.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNativeModeRejectsNegativeShiftCount(): void |
||||||
|
{ |
||||||
|
$this->expectException(TestError::class); |
||||||
|
$this->expectExceptionMessage('Bit shift by a negative number is not supported in native mode'); |
||||||
|
$this->compileNativeWithReporter('shift-boundary-native-negative.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNativeModeRejectsNegativeRightShift(): void |
||||||
|
{ |
||||||
|
$this->expectException(TestError::class); |
||||||
|
$this->expectExceptionMessage('Right shift of a negative value is implementation-defined in C++'); |
||||||
|
$this->compileNativeWithReporter('shift-boundary-native-neg-right.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNativeModeRejectsLeftShiftChangingSignBit(): void |
||||||
|
{ |
||||||
|
$this->expectException(TestError::class); |
||||||
|
$this->expectExceptionMessage('Left shift that changes the sign bit is undefined behavior in C++'); |
||||||
|
$this->compileNativeWithReporter('shift-boundary-native-sign-bit.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNativeModeRejectsNegativeLeftShift(): void |
||||||
|
{ |
||||||
|
$this->expectException(TestError::class); |
||||||
|
$this->expectExceptionMessage('Left shift of a negative value is undefined behavior in C++'); |
||||||
|
$this->compileNativeWithReporter('shift-boundary-native-neg-left.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNativeModeAllowsInRangeShift(): void |
||||||
|
{ |
||||||
|
$compiler = $this->compileNativeWithReporter('shift-boundary-native-ok.php'); |
||||||
|
$this->assertNotNull($compiler); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return object{warnings: list<string>} |
||||||
|
*/ |
||||||
|
private function compileWithReporter(): object |
||||||
|
{ |
||||||
|
global $translator; |
||||||
|
$compiler = CompilerTest::create(ROOT_PATH); |
||||||
|
$translator = $compiler; |
||||||
|
$reporter = new class implements DiagnosticReporter { |
||||||
|
/** @var list<string> */ |
||||||
|
public array $warnings = []; |
||||||
|
|
||||||
|
public function fatal(string $message): never |
||||||
|
{ |
||||||
|
throw new TestError($message); |
||||||
|
} |
||||||
|
|
||||||
|
public function warning(Node $node, string $file, string $message): void |
||||||
|
{ |
||||||
|
$this->warnings[] = $message; |
||||||
|
} |
||||||
|
}; |
||||||
|
$compiler->setDiagnosticReporter($reporter); |
||||||
|
|
||||||
|
$testFile = __DIR__ . '/../code/shift-boundary-warning.php'; |
||||||
|
$compiler->addFiles([$testFile]); |
||||||
|
$compiler->prepareFile($testFile); |
||||||
|
$compiler->convertFile($testFile); |
||||||
|
|
||||||
|
return $reporter; |
||||||
|
} |
||||||
|
|
||||||
|
private function compileNativeWithReporter(string $file): CompilerTest |
||||||
|
{ |
||||||
|
global $translator; |
||||||
|
$compiler = CompilerTest::create(ROOT_PATH); |
||||||
|
$translator = $compiler; |
||||||
|
$compiler->setDiagnosticReporter(new class implements DiagnosticReporter { |
||||||
|
public function fatal(string $message): never |
||||||
|
{ |
||||||
|
throw new TestError($message); |
||||||
|
} |
||||||
|
|
||||||
|
public function warning(Node $node, string $file, string $message): void |
||||||
|
{ |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$testFile = __DIR__ . '/../code/' . $file; |
||||||
|
$compiler->addFiles([$testFile]); |
||||||
|
$compiler->prepareFile($testFile); |
||||||
|
$compiler->convertFile($testFile); |
||||||
|
|
||||||
|
return $compiler; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
--TEST-- |
||||||
|
Constant bit shift boundaries follow PHP semantics |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(1 << 64); |
||||||
|
var_dump(1 >> 64); |
||||||
|
var_dump(-1 >> 64); |
||||||
|
var_dump(PHP_INT_MIN >> 64); |
||||||
|
var_dump(1 >> 63); |
||||||
|
var_dump(-1 >> 63); |
||||||
|
var_dump(1 << 63); |
||||||
|
var_dump(5 >> 1); |
||||||
|
try { |
||||||
|
var_dump(1 >> -1); |
||||||
|
} catch (ArithmeticError $e) { |
||||||
|
echo "caught: " . $e->getMessage() . "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECTF-- |
||||||
|
int(0) |
||||||
|
int(0) |
||||||
|
int(-1) |
||||||
|
int(-1) |
||||||
|
int(0) |
||||||
|
int(-1) |
||||||
|
int(-9223372036854775808) |
||||||
|
int(2) |
||||||
|
caught: Bit shift by negative number |
||||||
Loading…
Reference in new issue