- 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