TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
7.8 KiB
156 lines
7.8 KiB
<?php
|
|
|
|
namespace TypePhpTest\PythonTools;
|
|
|
|
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
|
|
use PHPUnit\Framework\TestCase;
|
|
use TypePhp\PythonTools\IdeHelper\HelperRenderer;
|
|
use TypePhp\PythonTools\IdeHelper\PhpyModuleScanner;
|
|
use TypePhp\PythonTools\IdeHelper\PyObjectHelperRenderer;
|
|
|
|
final class PythonIdeHelperTest extends TestCase
|
|
{
|
|
public function testRendererProducesInertNamespaceHelper(): void
|
|
{
|
|
$metadata = [
|
|
'module' => 'demo.widgets',
|
|
'doc' => 'Demo module.',
|
|
'attributes' => [
|
|
['name' => 'VERSION'],
|
|
['name' => 'class'],
|
|
],
|
|
'functions' => [
|
|
[
|
|
'name' => 'create',
|
|
'parameters' => [
|
|
['name' => 'value', 'optional' => false, 'variadic' => false],
|
|
['name' => 'mode', 'optional' => true, 'variadic' => false],
|
|
],
|
|
],
|
|
],
|
|
'classes' => [
|
|
[
|
|
'name' => 'Widget',
|
|
'parameters' => [],
|
|
'methods' => [
|
|
['name' => 'render', 'parameters' => []],
|
|
],
|
|
'properties' => ['name'],
|
|
],
|
|
],
|
|
];
|
|
|
|
$helper = (new HelperRenderer())->render($metadata);
|
|
|
|
self::assertStringContainsString('@generated TypePHP Python IDE helper', $helper);
|
|
self::assertStringContainsString('namespace python\\demo\\widgets;', $helper);
|
|
self::assertStringNotContainsString('if (false)', $helper);
|
|
self::assertStringEndsWith("die(\\PyObject::IDE_HELPER_ONLY);\n", $helper);
|
|
self::assertStringContainsString('const VERSION = new \\PyObject();', $helper);
|
|
self::assertStringContainsString("\nconst VERSION = new \\PyObject();", $helper);
|
|
self::assertStringNotContainsString('/** @var \\PyObject */', $helper);
|
|
self::assertStringContainsString("\nclass Widget extends \\PyObject", $helper);
|
|
self::assertStringContainsString("\n public function render()", $helper);
|
|
self::assertStringNotContainsString('const class =', $helper);
|
|
self::assertStringNotContainsString('define(', $helper);
|
|
self::assertStringContainsString(
|
|
'function create(mixed $value, mixed $mode = null): \\PyObject { die(\\PyObject::IDE_HELPER_ONLY); }',
|
|
$helper,
|
|
);
|
|
self::assertStringContainsString('class Widget extends \\PyObject', $helper);
|
|
self::assertStringContainsString('function Widget(): Widget { die(\\PyObject::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function __construct() { parent::__construct(); }', $helper);
|
|
self::assertStringContainsString('public function render(): \\PyObject { die(\\PyObject::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringNotContainsString('PyCore::import', $helper);
|
|
}
|
|
|
|
public function testBuiltinsAreRenderedInPythonRootNamespace(): void
|
|
{
|
|
$helper = (new HelperRenderer())->render([
|
|
'module' => 'builtins',
|
|
'doc' => '',
|
|
'attributes' => [],
|
|
'functions' => [['name' => 'len', 'parameters' => []]],
|
|
'classes' => [],
|
|
]);
|
|
|
|
self::assertStringContainsString('namespace python;', $helper);
|
|
self::assertStringContainsString('function len(): \\PyObject', $helper);
|
|
}
|
|
|
|
public function testRendererProducesAnInertPyObjectHelper(): void
|
|
{
|
|
$helper = (new PyObjectHelperRenderer())->render();
|
|
|
|
self::assertStringNotContainsString('if (false)', $helper);
|
|
self::assertStringEndsWith("die(PyObject::IDE_HELPER_ONLY);\n", $helper);
|
|
self::assertStringContainsString(
|
|
'class PyObject implements \\ArrayAccess, \\Iterator, \\Countable',
|
|
$helper,
|
|
);
|
|
self::assertStringContainsString("\nclass PyObject implements", $helper);
|
|
self::assertStringContainsString("\n public const IDE_HELPER_ONLY", $helper);
|
|
self::assertStringContainsString("public const IDE_HELPER_ONLY = 'IDE helper only';", $helper);
|
|
self::assertStringNotContainsString('enum PyObjectConstant', $helper);
|
|
self::assertStringContainsString('public function toArray(): array { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toValue(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function __toString(): string { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function next(): void {}', $helper);
|
|
self::assertStringContainsString('TypePHP keyword methods are compiler intrinsics', $helper);
|
|
self::assertStringContainsString('public function toInt(): int { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toFloat(): float { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toString(): string { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toBool(): bool { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toStream(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toBigInt(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toBigFloat(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toDecimal(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toObject(?string $class = null): object', $helper);
|
|
self::assertStringContainsString('public function toAny(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertStringContainsString('public function toRef(): mixed { die(self::IDE_HELPER_ONLY); }', $helper);
|
|
self::assertSame(1, substr_count($helper, 'public function toArray(): array'));
|
|
}
|
|
|
|
public function testRendererOmitsPythonCountMethodInheritedFromPyObject(): void
|
|
{
|
|
$helper = (new HelperRenderer())->render([
|
|
'module' => 'demo',
|
|
'attributes' => [],
|
|
'functions' => [],
|
|
'classes' => [[
|
|
'name' => 'Container',
|
|
'parameters' => [],
|
|
'properties' => [],
|
|
'methods' => [
|
|
['name' => 'count', 'parameters' => [['name' => 'value']]],
|
|
['name' => 'append', 'parameters' => [['name' => 'value']]],
|
|
],
|
|
]],
|
|
]);
|
|
|
|
self::assertStringNotContainsString('public function count(', $helper);
|
|
self::assertStringContainsString('public function append(mixed $value): \\PyObject', $helper);
|
|
}
|
|
|
|
#[RequiresPhpExtension('phpy')]
|
|
public function testPhpyScannerReadsRealPythonModule(): void
|
|
{
|
|
$metadata = (new PhpyModuleScanner())->scan('math');
|
|
$functions = array_column($metadata['functions'], null, 'name');
|
|
$attributes = array_column($metadata['attributes'], null, 'name');
|
|
|
|
self::assertArrayHasKey('sqrt', $functions);
|
|
self::assertSame('x', $functions['sqrt']['parameters'][0]['name']);
|
|
self::assertArrayHasKey('pi', $attributes);
|
|
}
|
|
|
|
#[RequiresPhpExtension('phpy')]
|
|
public function testPhpyScannerReadsClassesWithoutRetainingDynamicCallTrampolines(): void
|
|
{
|
|
$metadata = (new PhpyModuleScanner())->scan('json');
|
|
$classes = array_column($metadata['classes'], null, 'name');
|
|
|
|
self::assertArrayHasKey('JSONDecoder', $classes);
|
|
self::assertNotEmpty($classes['JSONDecoder']['methods']);
|
|
}
|
|
}
|
|
|