summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2019-11-08 15:38:00 +0100
committerNikita Popov <nikita.ppv@gmail.com>2019-11-08 17:32:18 +0100
commit9bbbc9e7e9ee1e3941065b32daccee389d61e785 (patch)
treeb147b80400f44f4a25224851a95dba330b517b73 /scripts
parent70b4bc963c559aba8d036a47804c8b21ca41a668 (diff)
downloadphp-git-9bbbc9e7e9ee1e3941065b32daccee389d61e785.tar.gz
Add support for union types in stubs
This is the MVP for supporting union types in PHP stubs. Return types with only builtin types work, which is the part we mainly need. Closes GH-4895.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/dev/gen_stub.php185
1 files changed, 148 insertions, 37 deletions
diff --git a/scripts/dev/gen_stub.php b/scripts/dev/gen_stub.php
index 4bf29fc440..2aed71af61 100755
--- a/scripts/dev/gen_stub.php
+++ b/scripts/dev/gen_stub.php
@@ -55,35 +55,32 @@ function processStubFile(string $stubFile) {
}
}
-class Type {
+class SimpleType {
/** @var string */
public $name;
/** @var bool */
public $isBuiltin;
- /** @var bool */
- public $isNullable;
- public function __construct(string $name, bool $isBuiltin, bool $isNullable = false) {
+ public function __construct(string $name, bool $isBuiltin) {
$this->name = $name;
$this->isBuiltin = $isBuiltin;
- $this->isNullable = $isNullable;
}
public static function fromNode(Node $node) {
- if ($node instanceof Node\NullableType) {
- $type = self::fromNode($node->type);
- return new Type($type->name, $type->isBuiltin, true);
- }
if ($node instanceof Node\Name) {
assert($node->isFullyQualified());
- return new Type($node->toString(), false);
+ return new SimpleType($node->toString(), false);
}
if ($node instanceof Node\Identifier) {
- return new Type($node->toString(), true);
+ return new SimpleType($node->toString(), true);
}
throw new Exception("Unexpected node type");
}
+ public function isNull() {
+ return $this->isBuiltin && $this->name === 'null';
+ }
+
public function toTypeCode() {
assert($this->isBuiltin);
switch (strtolower($this->name)) {
@@ -108,14 +105,111 @@ class Type {
}
}
+ public function toTypeMask() {
+ assert($this->isBuiltin);
+ switch (strtolower($this->name)) {
+ case "false":
+ return "MAY_BE_FALSE";
+ case "bool":
+ return "MAY_BE_BOOL";
+ case "int":
+ return "MAY_BE_LONG";
+ case "float":
+ return "MAY_BE_DOUBLE";
+ case "string":
+ return "MAY_BE_STRING";
+ case "array":
+ return "MAY_BE_ARRAY";
+ case "object":
+ return "MAY_BE_OBJECT";
+ case "callable":
+ return "MAY_BE_CALLABLE";
+ default:
+ throw new Exception("Not implemented: $this->name");
+ }
+ }
+
+ public function equals(SimpleType $other) {
+ return $this->name === $other->name
+ && $this->isBuiltin === $other->isBuiltin;
+ }
+}
+
+class Type {
+ /** @var SimpleType[] $types */
+ public $types;
+
+ public function __construct(array $types) {
+ $this->types = $types;
+ }
+
+ public static function fromNode(Node $node) {
+ if ($node instanceof Node\UnionType) {
+ return new Type(array_map(['SimpleType', 'fromNode'], $node->types));
+ }
+ if ($node instanceof Node\NullableType) {
+ return new Type([
+ SimpleType::fromNode($node->type),
+ new SimpleType('null', true),
+ ]);
+ }
+ return new Type([SimpleType::fromNode($node)]);
+ }
+
+ public function isNullable(): bool {
+ foreach ($this->types as $type) {
+ if ($type->isNull()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function isBuiltinOnly(): bool {
+ foreach ($this->types as $type) {
+ if (!$type->isBuiltin) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function getWithoutNull(): Type {
+ return new Type(array_filter($this->types, function(SimpleType $type) {
+ return !$type->isNull();
+ }));
+ }
+
+ public function tryToSimpleType(): ?SimpleType {
+ $withoutNull = $this->getWithoutNull();
+ if (count($withoutNull->types) === 1) {
+ return $withoutNull->types[0];
+ }
+ return null;
+ }
+
+ public function toTypeMask(): string {
+ return implode('|', array_map(function(SimpleType $type) {
+ return $type->toTypeMask();
+ }, $this->types));
+ }
+
public static function equals(?Type $a, ?Type $b): bool {
if ($a === null || $b === null) {
return $a === $b;
}
- return $a->name === $b->name
- && $a->isBuiltin === $b->isBuiltin
- && $a->isNullable === $b->isNullable;
+ if (count($a->types) !== count($b->types)) {
+ return false;
+ }
+
+ for ($i = 0; $i < count($a->types); $i++) {
+ if (!$a->types[$i]->equals($b->types[$i])) {
+ return false;
+ }
+ }
+
+ return true;
}
}
@@ -369,20 +463,31 @@ function parseStubFile(string $fileName) {
function funcInfoToCode(FuncInfo $funcInfo): string {
$code = '';
- if ($funcInfo->return->type) {
- $returnType = $funcInfo->return->type;
- if ($returnType->isBuiltin) {
+ $returnType = $funcInfo->return->type;
+ if ($returnType !== null) {
+ $simpleReturnType = $returnType->tryToSimpleType();
+ if ($simpleReturnType !== null) {
+ if ($simpleReturnType->isBuiltin) {
+ $code .= sprintf(
+ "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_%s, %d, %d, %s, %d)\n",
+ $funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
+ $simpleReturnType->toTypeCode(), $returnType->isNullable()
+ );
+ } else {
+ $code .= sprintf(
+ "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_%s, %d, %d, %s, %d)\n",
+ $funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
+ str_replace('\\', '\\\\', $simpleReturnType->name), $returnType->isNullable()
+ );
+ }
+ } else if ($returnType->isBuiltinOnly()) {
$code .= sprintf(
- "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_%s, %d, %d, %s, %d)\n",
+ "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_%s, %d, %d, %s)\n",
$funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
- $returnType->toTypeCode(), $returnType->isNullable
+ $returnType->toTypeMask()
);
} else {
- $code .= sprintf(
- "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_%s, %d, %d, %s, %d)\n",
- $funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
- str_replace('\\', '\\\\', $returnType->name), $returnType->isNullable
- );
+ throw new Exception('Unimplemented');
}
} else {
$code .= sprintf(
@@ -393,19 +498,25 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
foreach ($funcInfo->args as $argInfo) {
$argKind = $argInfo->isVariadic ? "ARG_VARIADIC" : "ARG";
- if ($argInfo->type) {
- if ($argInfo->type->isBuiltin) {
- $code .= sprintf(
- "\tZEND_%s_TYPE_INFO(%s, %s, %s, %d)\n",
- $argKind, $argInfo->getSendByString(), $argInfo->name,
- $argInfo->type->toTypeCode(), $argInfo->type->isNullable
- );
+ $argType = $argInfo->type;
+ if ($argType !== null) {
+ $simpleArgType = $argType->tryToSimpleType();
+ if ($simpleArgType !== null) {
+ if ($simpleArgType->isBuiltin) {
+ $code .= sprintf(
+ "\tZEND_%s_TYPE_INFO(%s, %s, %s, %d)\n",
+ $argKind, $argInfo->getSendByString(), $argInfo->name,
+ $simpleArgType->toTypeCode(), $argType->isNullable()
+ );
+ } else {
+ $code .= sprintf(
+ "\tZEND_%s_OBJ_INFO(%s, %s, %s, %d)\n",
+ $argKind, $argInfo->getSendByString(), $argInfo->name,
+ str_replace('\\', '\\\\', $simpleArgType->name), $argType->isNullable()
+ );
+ }
} else {
- $code .= sprintf(
- "\tZEND_%s_OBJ_INFO(%s, %s, %s, %d)\n",
- $argKind, $argInfo->getSendByString(), $argInfo->name,
- str_replace('\\', '\\\\', $argInfo->type->name), $argInfo->type->isNullable
- );
+ throw new Exception('Unimplemented');
}
} else {
$code .= sprintf(
@@ -456,7 +567,7 @@ function generateArginfoCode(array $funcInfos): string {
}
function initPhpParser() {
- $version = "4.2.2";
+ $version = "4.3.0";
$phpParserDir = __DIR__ . "/PHP-Parser-$version";
if (!is_dir($phpParserDir)) {
$cwd = getcwd();