- Support single array parameter initialization for std::array, std::vector, std::map, std::orderedMap, std::list, and std::dict - Implement type inference from array initializer expressions with exact type matching - Add rectangular shape validation for std::array initializers - Generate appropriate C++ code for value initializers using lambda expressions - Update documentation to reflect new value initializer capabilities - Add comprehensive tests for valid and invalid initializer scenarios - Defer StdFunc/StdArgInfo implementation as documented in design recordsmaster
parent
9d68d2803c
commit
5df39f2b4f
11 changed files with 522 additions and 14 deletions
@ -0,0 +1,74 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider; |
||||||
|
use TypePhp\CompilerTest; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
use TypePhp\Exception\TestError; |
||||||
|
|
||||||
|
final class StdContainerValueInitializerTest extends BaseTest |
||||||
|
{ |
||||||
|
private function translate(string $source): string |
||||||
|
{ |
||||||
|
global $translator; |
||||||
|
$directory = sys_get_temp_dir() . '/std-value-initializer-' . bin2hex(random_bytes(6)); |
||||||
|
mkdir($directory); |
||||||
|
$file = $directory . '/initializer.php'; |
||||||
|
file_put_contents($file, '<?php function main(): void { ' . $source . ' }'); |
||||||
|
try { |
||||||
|
$translator = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||||
|
$translator->addFiles([$file]); |
||||||
|
$translator->prepareFile($file); |
||||||
|
return file_get_contents($translator->convertFile($file)); |
||||||
|
} finally { |
||||||
|
unlink($file); |
||||||
|
rmdir($directory); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function testEveryContainerAcceptsAnInferredArrayInitializer(): void |
||||||
|
{ |
||||||
|
$code = $this->translate(<<<'PHP' |
||||||
|
$n = 2; |
||||||
|
$a = std::array([[1, $n], [3, 4]]); |
||||||
|
$v = std::vector([1, $n]); |
||||||
|
$m = std::map(['a' => 1, 'b' => $n]); |
||||||
|
$o = std::orderedMap([1 => 'a', 2 => 'b']); |
||||||
|
$l = std::list([1, $n]); |
||||||
|
$d = std::dict(['a' => 1, 'b' => $n]); |
||||||
|
PHP); |
||||||
|
self::assertStringContainsString('php::StdArray<php::StdArray<php::Int, 2>, 2>', $code); |
||||||
|
self::assertStringContainsString('a_ref.offsetGet(0).offsetSet(1, php::toInt(n))', $code); |
||||||
|
self::assertStringContainsString('v_ref.push_back(php::toInt(n))', $code); |
||||||
|
self::assertStringContainsString('m_ref.offsetSet(', $code); |
||||||
|
self::assertStringContainsString('o_ref.offsetSet(', $code); |
||||||
|
self::assertStringContainsString('l = php::Array{', $code); |
||||||
|
self::assertStringContainsString('d = php::Array{', $code); |
||||||
|
} |
||||||
|
|
||||||
|
#[DataProvider('invalidInitializers')] |
||||||
|
public function testInvalidInitializers(string $source, string $message): void |
||||||
|
{ |
||||||
|
try { |
||||||
|
$this->translate($source); |
||||||
|
self::fail('Invalid std value initializer was accepted'); |
||||||
|
} catch (TestError|SyntaxError $error) { |
||||||
|
self::assertStringContainsString($message, $error->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function invalidInitializers(): iterable |
||||||
|
{ |
||||||
|
yield 'empty' => ['$v = std::vector([]);', 'cannot infer types from an empty array']; |
||||||
|
yield 'mixed values' => ['$v = std::vector([1, "2"]);', 'values must all have exactly the same type']; |
||||||
|
yield 'mixed keys' => ['$m = std::map([1 => 1, "2" => 2]);', 'keys must all have exactly the same type']; |
||||||
|
yield 'float key' => ['$m = std::map([1.5 => 1]);', 'statically known int or string type']; |
||||||
|
yield 'bool key' => ['$m = std::map([true => 1]);', 'statically known int or string type']; |
||||||
|
yield 'null key' => ['$m = std::map([null => 1]);', 'statically known int or string type']; |
||||||
|
yield 'map missing key' => ['$m = std::map([1, 2]);', 'requires an explicit key']; |
||||||
|
yield 'ragged array' => ['$a = std::array([[1], [2, 3]]);', 'uniform rectangular shape']; |
||||||
|
yield 'list string key' => ['$l = std::list(["a" => 1]);', 'keys must have type int']; |
||||||
|
yield 'dict missing key' => ['$d = std::dict([1, 2]);', 'requires an explicit key']; |
||||||
|
yield 'dynamic key' => ['$key = std::any("a"); $d = std::dict([$key => 1]);', 'statically known int or string type']; |
||||||
|
yield 'dynamic value' => ['$x = std::any(1); $l = std::list([$x]);', 'cannot infer an element type from var/any']; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,88 @@ |
|||||||
|
--TEST-- |
||||||
|
All std containers infer their contracts from array value initializers |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function typed_int(int $value): int |
||||||
|
{ |
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
function typed_string(string $value): string |
||||||
|
{ |
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
function next_value(int &$counter): int |
||||||
|
{ |
||||||
|
return ++$counter; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$base = 2; |
||||||
|
$array = std::array([ |
||||||
|
[1, $base + 1, typed_int(4)], |
||||||
|
[0, typed_int(2), 4], |
||||||
|
]); |
||||||
|
var_dump(count($array), count($array[0]), $array[0][1], $array[1][1]); |
||||||
|
|
||||||
|
$vector = std::vector([typed_int(7), $base + 6, 9]); |
||||||
|
var_dump(count($vector), $vector[0], $vector[1], $vector[2]); |
||||||
|
|
||||||
|
$counter = 0; |
||||||
|
$evaluated = std::vector([next_value($counter), next_value($counter)]); |
||||||
|
var_dump($evaluated[0], $evaluated[1], $counter); |
||||||
|
|
||||||
|
$map = std::map([ |
||||||
|
typed_string('v1') => typed_int(999), |
||||||
|
'v2' => $base + 998, |
||||||
|
]); |
||||||
|
var_dump($map['v1'], $map['v2']); |
||||||
|
|
||||||
|
$intMap = std::map([ |
||||||
|
typed_int(10) => typed_string('ten'), |
||||||
|
20 => 'twenty', |
||||||
|
]); |
||||||
|
var_dump($intMap[10], $intMap[20]); |
||||||
|
|
||||||
|
$ordered = std::orderedMap([ |
||||||
|
20 => typed_string('twenty'), |
||||||
|
typed_int(10) => 'ten', |
||||||
|
]); |
||||||
|
var_dump($ordered[10], $ordered[20]); |
||||||
|
|
||||||
|
$list = std::list([typed_int(9), $base + 1, 5]); |
||||||
|
var_dump(is_array($list), $list[0], $list[1], $list[2]); |
||||||
|
|
||||||
|
$dict = std::dict([ |
||||||
|
typed_string('v1') => typed_int(999), |
||||||
|
'v2' => $base + 998, |
||||||
|
]); |
||||||
|
var_dump(is_array($dict), $dict['v1'], $dict['v2']); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(2) |
||||||
|
int(3) |
||||||
|
int(3) |
||||||
|
int(2) |
||||||
|
int(3) |
||||||
|
int(7) |
||||||
|
int(8) |
||||||
|
int(9) |
||||||
|
int(1) |
||||||
|
int(2) |
||||||
|
int(2) |
||||||
|
int(999) |
||||||
|
int(1000) |
||||||
|
string(3) "ten" |
||||||
|
string(6) "twenty" |
||||||
|
string(3) "ten" |
||||||
|
string(6) "twenty" |
||||||
|
bool(true) |
||||||
|
int(9) |
||||||
|
int(3) |
||||||
|
int(5) |
||||||
|
bool(true) |
||||||
|
int(999) |
||||||
|
int(1000) |
||||||
Loading…
Reference in new issue