parent
a182a2cde0
commit
9f6e31c539
6 changed files with 540 additions and 42 deletions
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
function optimizerDynamicBool(): mixed |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function optimizerTypedBool(): bool |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function optimizerTypedInt(): int |
||||||
|
{ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
function optimizerTypedFloat(): float |
||||||
|
{ |
||||||
|
return 0.0; |
||||||
|
} |
||||||
|
|
||||||
|
function optimizerTypedArgumentCalls(): void |
||||||
|
{ |
||||||
|
in_array('1', [1], optimizerTypedBool()); |
||||||
|
hypot(optimizerTypedInt(), optimizerTypedFloat()); |
||||||
|
in_array('1', [1], optimizerTypedInt()); |
||||||
|
in_array('1', [1], optimizerDynamicBool()); |
||||||
|
strlen(null); |
||||||
|
json_decode('null', null); |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use TypePhp\CompilerTest; |
||||||
|
|
||||||
|
final class FuncCallOptimizerTest extends BaseTest |
||||||
|
{ |
||||||
|
public function testStrictTypedArgumentsUseOnlyProvenDirectAbiPaths(): void |
||||||
|
{ |
||||||
|
global $translator; |
||||||
|
|
||||||
|
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||||
|
$translator = $compiler; |
||||||
|
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/func-call-optimizer-typed-arguments.php'; |
||||||
|
$compiler->addFiles([$source]); |
||||||
|
$compiler->prepareFile($source); |
||||||
|
$generated = $compiler->convertFile($source); |
||||||
|
$code = file_get_contents($generated); |
||||||
|
|
||||||
|
self::assertIsString($code); |
||||||
|
self::assertSame(1, substr_count($code, 'php::fn::in_array(')); |
||||||
|
self::assertSame(1, substr_count($code, 'php::fn::hypot(')); |
||||||
|
self::assertSame(1, substr_count($code, 'php::fn::json_decode(')); |
||||||
|
self::assertSame(3, substr_count($code, 'php::call(')); |
||||||
|
self::assertStringContainsString('php_optimizertypedbool()', $code); |
||||||
|
self::assertStringContainsString('php_optimizertypedint()', $code); |
||||||
|
self::assertStringContainsString('php_optimizertypedfloat()', $code); |
||||||
|
self::assertStringContainsString('php_optimizerdynamicbool()', $code); |
||||||
|
self::assertMatchesRegularExpression('/php_optimizertypedbool\(\);\s*php::fn::in_array/', $code); |
||||||
|
self::assertMatchesRegularExpression('/php_optimizertypedint\(\);\s*php::call/', $code); |
||||||
|
self::assertMatchesRegularExpression('/php_optimizerdynamicbool\(\);\s*php::call/', $code); |
||||||
|
self::assertStringContainsString('php::fn::hypot(php::toFloat(', $code); |
||||||
|
self::assertStringContainsString('php::ArgList{php::null}', $code); |
||||||
|
self::assertMatchesRegularExpression('/php::fn::json_decode\([^;]+php::null\);/', $code); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,52 +1,70 @@ |
|||||||
--TEST-- |
--TEST-- |
||||||
Passing null to optional parameters should use C++ default values |
Nullable and non-nullable builtin parameters preserve strict null semantics |
||||||
--FILE-- |
--FILE-- |
||||||
<?php |
<?php |
||||||
error_reporting(E_ALL & ~E_DEPRECATED); |
declare(strict_types=1); |
||||||
|
|
||||||
// substr: null length should take rest of string, not return empty |
function main() |
||||||
$s = 'hello world'; |
{ |
||||||
var_dump(substr($s, 6, null)); |
$s = 'hello world'; |
||||||
var_dump(substr($s, 6) === substr($s, 6, null)); |
var_dump(substr($s, 6, null)); |
||||||
var_dump(substr($s, 0, null) === $s); |
var_dump(substr($s, 6) === substr($s, 6, null)); |
||||||
var_dump(substr($s, null) === $s); |
var_dump(substr($s, 0, null) === $s); |
||||||
|
|
||||||
// strpos: null offset should default to 0 |
try { |
||||||
var_dump(strpos($s, 'o', null)); |
substr($s, null); |
||||||
var_dump(strpos($s, 'o', null) === strpos($s, 'o')); |
echo "substr-offset-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "substr-offset-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
// stripos: null offset should default to 0 |
try { |
||||||
var_dump(stripos($s, 'O', null)); |
strpos($s, 'o', null); |
||||||
var_dump(stripos($s, 'O', null) === stripos($s, 'O')); |
echo "strpos-offset-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "strpos-offset-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
// strrpos: null offset should default to 0 (search from end) |
try { |
||||||
var_dump(strrpos('hello hello', 'o', null)); |
stripos($s, 'O', null); |
||||||
var_dump(strrpos('hello hello', 'o', null) === strrpos('hello hello', 'o')); |
echo "stripos-offset-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "stripos-offset-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
// strstr: null before_needle should default to false |
try { |
||||||
var_dump(strstr($s, 'o', null)); |
strrpos('hello hello', 'o', null); |
||||||
var_dump(strstr($s, 'o', null) === strstr($s, 'o')); |
echo "strrpos-offset-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "strrpos-offset-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
// str_repeat: ensure non-null still works |
try { |
||||||
var_dump(str_repeat('ab', 3)); |
strstr($s, 'o', null); |
||||||
|
echo "strstr-before-needle-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "strstr-before-needle-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
// explode with null limit: null coerces to 0 (limit=0: whole string as single element) |
var_dump(str_repeat('ab', 3)); |
||||||
$arr = explode(' ', 'a b c d', null); |
|
||||||
var_dump(count($arr)); |
try { |
||||||
|
explode(' ', 'a b c d', null); |
||||||
|
echo "explode-limit-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "explode-limit-null=TypeError\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
?> |
?> |
||||||
--EXPECT-- |
--EXPECT-- |
||||||
string(5) "world" |
string(5) "world" |
||||||
bool(true) |
bool(true) |
||||||
bool(true) |
bool(true) |
||||||
bool(true) |
substr-offset-null=TypeError |
||||||
int(4) |
strpos-offset-null=TypeError |
||||||
bool(true) |
stripos-offset-null=TypeError |
||||||
int(4) |
strrpos-offset-null=TypeError |
||||||
bool(true) |
strstr-before-needle-null=TypeError |
||||||
int(10) |
|
||||||
bool(true) |
|
||||||
string(7) "o world" |
|
||||||
bool(true) |
|
||||||
string(6) "ababab" |
string(6) "ababab" |
||||||
int(1) |
explode-limit-null=TypeError |
||||||
|
|||||||
@ -0,0 +1,316 @@ |
|||||||
|
--TEST-- |
||||||
|
Optimized builtins preserve strict typed parameter validation |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
function mixedBool(): mixed |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function typedBool(): bool |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function typedInt(): int |
||||||
|
{ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
function typedFloat(): float |
||||||
|
{ |
||||||
|
return 1.0; |
||||||
|
} |
||||||
|
|
||||||
|
function mixedInt(): mixed |
||||||
|
{ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
function mixedArray(): mixed |
||||||
|
{ |
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
function unionInt(): bool|int |
||||||
|
{ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
function mixedString(): mixed |
||||||
|
{ |
||||||
|
return '1'; |
||||||
|
} |
||||||
|
|
||||||
|
function orderedNeedle(array &$events): mixed |
||||||
|
{ |
||||||
|
$events[] = 'needle'; |
||||||
|
return '1'; |
||||||
|
} |
||||||
|
|
||||||
|
function orderedHaystack(array &$events): mixed |
||||||
|
{ |
||||||
|
$events[] = 'haystack'; |
||||||
|
return [1]; |
||||||
|
} |
||||||
|
|
||||||
|
function orderedStrict(array &$events): mixed |
||||||
|
{ |
||||||
|
$events[] = 'strict'; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
var_dump(in_array('1', [1], true)); |
||||||
|
var_dump(in_array('1', [1], typedBool())); |
||||||
|
var_dump(in_array('1', [1], mixedBool())); |
||||||
|
var_dump(array_search('1', [1], mixedBool())); |
||||||
|
var_dump(strlen('abc')); |
||||||
|
var_dump(strlen((string) mixedInt())); |
||||||
|
var_dump(hypot(mixedInt(), 0.0)); |
||||||
|
var_dump(hypot(typedInt(), 0.0)); |
||||||
|
var_dump(hypot(3, 4)); |
||||||
|
var_dump(json_decode('"ok"', mixedBool())); |
||||||
|
var_dump(json_decode('null', null)); |
||||||
|
var_dump(substr('abc', 1, null)); |
||||||
|
var_dump(strlen(json_decode('"abc"', mixedBool()))); |
||||||
|
var_dump(array_merge(mixedArray(), ['value' => 1])); |
||||||
|
var_dump(array_keys(mixedArray())); |
||||||
|
var_dump(function_exists(mixedString())); |
||||||
|
var_dump(round(...[1.25, 1])); |
||||||
|
|
||||||
|
$events = []; |
||||||
|
var_dump(in_array( |
||||||
|
orderedNeedle($events), |
||||||
|
orderedHaystack($events), |
||||||
|
orderedStrict($events) |
||||||
|
)); |
||||||
|
var_dump($events); |
||||||
|
|
||||||
|
try { |
||||||
|
in_array('1', [1], mixedInt()); |
||||||
|
echo "in-array-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "in-array-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
in_array('1', [1], typedInt()); |
||||||
|
echo "in-array-typed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "in-array-typed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
strlen(mixedInt()); |
||||||
|
echo "strlen-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "strlen-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
strlen(null); |
||||||
|
echo "strlen-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "strlen-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
array_fill(mixedString(), 1, 'x'); |
||||||
|
echo "array-fill-mixed-string=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "array-fill-mixed-string=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
array_fill(typedFloat(), 1, 'x'); |
||||||
|
echo "array-fill-typed-float=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "array-fill-typed-float=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
hypot(0.0, mixedString()); |
||||||
|
echo "hypot-mixed-string=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "hypot-mixed-string=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
json_decode('null', typedInt()); |
||||||
|
echo "json-decode-typed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "json-decode-typed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
strpos('abc', 'b', null); |
||||||
|
echo "strpos-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "strpos-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
in_array('1', mixedInt(), true); |
||||||
|
echo "in-array-mixed-haystack=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "in-array-mixed-haystack=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
is_callable('strlen', mixedInt()); |
||||||
|
echo "is-callable-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "is-callable-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
array_merge(mixedInt()); |
||||||
|
echo "array-merge-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "array-merge-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
array_search('1', [1], mixedArray()); |
||||||
|
echo "array-search-mixed-array=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "array-search-mixed-array=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
in_array('1', [1], unionInt()); |
||||||
|
echo "in-array-union-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "in-array-union-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
array_key_exists('key', mixedInt()); |
||||||
|
echo "array-key-exists-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "array-key-exists-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
array_keys(mixedInt()); |
||||||
|
echo "array-keys-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "array-keys-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
round(1.25, mixedString()); |
||||||
|
echo "round-mixed-string=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "round-mixed-string=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
round(1.25, typedFloat()); |
||||||
|
echo "round-typed-float=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "round-typed-float=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
round(1.25, null); |
||||||
|
echo "round-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "round-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
count([], mixedBool()); |
||||||
|
echo "count-mixed-bool=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "count-mixed-bool=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
count([], typedBool()); |
||||||
|
echo "count-typed-bool=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "count-typed-bool=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
count([], null); |
||||||
|
echo "count-null=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "count-null=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
function_exists(mixedInt()); |
||||||
|
echo "function-exists-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "function-exists-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
define(mixedInt(), 1); |
||||||
|
echo "define-mixed-int=missing TypeError\n"; |
||||||
|
} catch (TypeError $error) { |
||||||
|
echo "define-mixed-int=TypeError\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
bool(false) |
||||||
|
bool(false) |
||||||
|
bool(false) |
||||||
|
bool(false) |
||||||
|
int(3) |
||||||
|
int(1) |
||||||
|
float(1) |
||||||
|
float(1) |
||||||
|
float(5) |
||||||
|
string(2) "ok" |
||||||
|
NULL |
||||||
|
string(2) "bc" |
||||||
|
int(3) |
||||||
|
array(1) { |
||||||
|
["value"]=> |
||||||
|
int(1) |
||||||
|
} |
||||||
|
array(0) { |
||||||
|
} |
||||||
|
bool(false) |
||||||
|
float(1.3) |
||||||
|
bool(false) |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
string(6) "needle" |
||||||
|
[1]=> |
||||||
|
string(8) "haystack" |
||||||
|
[2]=> |
||||||
|
string(6) "strict" |
||||||
|
} |
||||||
|
in-array-mixed-int=TypeError |
||||||
|
in-array-typed-int=TypeError |
||||||
|
strlen-mixed-int=TypeError |
||||||
|
strlen-null=TypeError |
||||||
|
array-fill-mixed-string=TypeError |
||||||
|
array-fill-typed-float=TypeError |
||||||
|
hypot-mixed-string=TypeError |
||||||
|
json-decode-typed-int=TypeError |
||||||
|
strpos-null=TypeError |
||||||
|
in-array-mixed-haystack=TypeError |
||||||
|
is-callable-mixed-int=TypeError |
||||||
|
array-merge-mixed-int=TypeError |
||||||
|
array-search-mixed-array=TypeError |
||||||
|
in-array-union-int=TypeError |
||||||
|
array-key-exists-mixed-int=TypeError |
||||||
|
array-keys-mixed-int=TypeError |
||||||
|
round-mixed-string=TypeError |
||||||
|
round-typed-float=TypeError |
||||||
|
round-null=TypeError |
||||||
|
count-mixed-bool=TypeError |
||||||
|
count-typed-bool=TypeError |
||||||
|
count-null=TypeError |
||||||
|
function-exists-mixed-int=TypeError |
||||||
|
define-mixed-int=TypeError |
||||||
Loading…
Reference in new issue