Skip to content

Commit

Permalink
Retrieve type info from PHPDoc and add support for proper union types…
Browse files Browse the repository at this point in the history
… and variadic params
  • Loading branch information
kocsismate committed Oct 23, 2020
1 parent 38da56c commit f8b2d2b
Show file tree
Hide file tree
Showing 62 changed files with 615 additions and 59 deletions.
184 changes: 145 additions & 39 deletions build/gen_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function __construct(string $name, bool $isBuiltin) {
$this->isBuiltin = $isBuiltin;
}

public static function fromNode(Node $node) {
public static function fromNode(Node $node): SimpleType {
if ($node instanceof Node\Name) {
if ($node->toLowerString() === 'static') {
// PHP internally considers "static" a builtin type.
Expand All @@ -146,11 +146,48 @@ public static function fromNode(Node $node) {
throw new Exception("Unexpected node type");
}

public function isNull() {
public static function fromPhpDoc(string $type): SimpleType
{
switch (strtolower($type)) {
case "void":
case "null":
case "false":
case "bool":
case "int":
case "float":
case "string":
case "array":
case "iterable":
case "object":
case "resource":
case "mixed":
case "self":
case "static":
return new SimpleType(strtolower($type), true);
}

if (strpos($type, "[]") !== false) {
return new SimpleType("array", true);
}

return new SimpleType($type, false);
}

public static function null(): SimpleType
{
return new SimpleType("null", true);
}

public static function void(): SimpleType
{
return new SimpleType("void", true);
}

public function isNull(): bool {
return $this->isBuiltin && $this->name === 'null';
}

public function toTypeCode() {
public function toTypeCode(): string {
assert($this->isBuiltin);
switch (strtolower($this->name)) {
case "bool":
Expand Down Expand Up @@ -228,19 +265,30 @@ public function __construct(array $types) {
$this->types = $types;
}

public static function fromNode(Node $node) {
public static function fromNode(Node $node): Type {
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),
SimpleType::null(),
]);
}
return new Type([SimpleType::fromNode($node)]);
}

public static function fromPhpDoc(string $phpDocType) {
$types = explode("|", $phpDocType);

$simpleTypes = [];
foreach ($types as $type) {
$simpleTypes[] = SimpleType::fromPhpDoc($type);
}

return new Type($simpleTypes);
}

public function isNullable(): bool {
foreach ($this->types as $type) {
if ($type->isNull()) {
Expand Down Expand Up @@ -341,14 +389,17 @@ class ArgInfo {
public $isVariadic;
/** @var Type|null */
public $type;
/** @var Type|null */
public $phpDocType;
/** @var string|null */
public $defaultValue;

public function __construct(string $name, int $sendBy, bool $isVariadic, ?Type $type, ?string $defaultValue) {
public function __construct(string $name, int $sendBy, bool $isVariadic, ?Type $type, ?Type $phpDocType, ?string $defaultValue) {
$this->name = $name;
$this->sendBy = $sendBy;
$this->isVariadic = $isVariadic;
$this->type = $type;
$this->phpDocType = $phpDocType;
$this->defaultValue = $defaultValue;
}

Expand All @@ -372,20 +423,19 @@ public function getSendByString(): string {
throw new Exception("Invalid sendBy value");
}

public function getTypeAsMethodsynopsisString(): string {
if ($this->type === null) {
return 'mixed';
}

return $this->type->__toString();
/**
* @return SimpleType[]
*/
public function getMethodsynopsisType(): array {
return $this->type->types ?? $this->phpDocType->types;
}

public function hasDefaultValue(): bool {
public function hasProperDefaultValue(): bool {
return $this->defaultValue !== null && $this->defaultValue !== "UNKNOWN";
}

public function getDefaultValueAsArginfoString(): string {
if ($this->hasDefaultValue()) {
if ($this->hasProperDefaultValue()) {
return '"' . addslashes($this->defaultValue) . '"';
}

Expand Down Expand Up @@ -525,23 +575,25 @@ class ReturnInfo {
public $byRef;
/** @var Type|null */
public $type;
/** @var Type|null */
public $phpDocType;

public function __construct(bool $byRef, ?Type $type) {
public function __construct(bool $byRef, ?Type $type, ?Type $phpDocType) {
$this->byRef = $byRef;
$this->type = $type;
$this->phpDocType = $phpDocType;
}

public function equals(ReturnInfo $other): bool {
return $this->byRef === $other->byRef
&& Type::equals($this->type, $other->type);
}

public function getTypeAsMethodsynopsisString(): string {
if ($this->type === null) {
return 'mixed';
}

return $this->type->__toString();
/**
* @return SimpleType[]
*/
public function getMethodsynopsisType(): array {
return $this->type->types ?? ($this->phpDocType->types ?? [SimpleType::void()]);
}
}

Expand Down Expand Up @@ -836,22 +888,42 @@ public function getValue(): string {
return $this->value;
}

public function getType(): string {
if (!$this->value) {
throw new Exception("@$this->name doesn't have any value");
}

$matches = [];

if ($this->name === "param") {
preg_match('/^\s*([\w\|\\\\\[\]]+)\s*\$\w+.*$/', $this->value, $matches);
} elseif ($this->name === "return") {
preg_match('/^\s*([\w\|\\\\\[\]]+)\s*$/', $this->value, $matches);
}

if (isset($matches[1]) === false) {
throw new Exception("@$this->name doesn't contain a type or has an invalid format \"$this->value\"");
}

return $matches[1];
}

public function getVariableName(): string {
$value = $this->getValue();
$value = $this->value;
if ($value === null || strlen($value) === 0) {
throw new Exception("@$this->name doesn't have any value");
}

$matches = [];

if ($this->name === "param") {
preg_match('/^\s*[\w\|\\\\]+\s*\$(\w+).*$/', $value, $matches);
preg_match('/^\s*[\w\| \\\\\[\]]+\s*\$(\w+).*$/', $value, $matches);
} elseif ($this->name === "prefer-ref") {
preg_match('/^\s*\$(\w+).*$/', $value, $matches);
}

if (isset($matches[1]) === false) {
throw new Exception("@$this->name doesn't contain variable name or has an invalid format \"$value\"");
throw new Exception("@$this->name doesn't contain a variable name or has an invalid format \"$value\"");
}

return $matches[1];
Expand Down Expand Up @@ -886,7 +958,7 @@ function parseFunctionLike(
$alias = null;
$isDeprecated = false;
$verify = true;
$haveDocReturnType = false;
$docReturnType = null;
$docParamTypes = [];

if ($comment) {
Expand All @@ -911,9 +983,9 @@ function parseFunctionLike(
} else if ($tag->name === 'no-verify') {
$verify = false;
} else if ($tag->name === 'return') {
$haveDocReturnType = true;
$docReturnType = $tag->getType();
} else if ($tag->name === 'param') {
$docParamTypes[$tag->getVariableName()] = true;
$docParamTypes[$tag->getVariableName()] = $tag->getType();
}
}
}
Expand Down Expand Up @@ -967,6 +1039,7 @@ function parseFunctionLike(
$sendBy,
$param->variadic,
$type,
isset($docParamTypes[$varName]) ? Type::fromPhpDoc($docParamTypes[$varName]) : null,
$param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null
);
if (!$param->default && !$param->variadic) {
Expand All @@ -979,13 +1052,14 @@ function parseFunctionLike(
}

$returnType = $func->getReturnType();
if ($returnType === null && !$haveDocReturnType && !$name->isMagicMethod()) {
if ($returnType === null && $docReturnType === null && !$name->isMagicMethod()) {
throw new Exception("Missing return type for function $name()");
}

$return = new ReturnInfo(
$func->returnsByRef(),
$returnType ? Type::fromNode($returnType) : null
$returnType ? Type::fromNode($returnType) : null,
$docReturnType ? Type::fromPhpDoc($docReturnType) : null
);

return new FuncInfo(
Expand Down Expand Up @@ -1200,7 +1274,7 @@ function funcInfoToCode(FuncInfo $funcInfo): string {

foreach ($funcInfo->args as $argInfo) {
$argKind = $argInfo->isVariadic ? "ARG_VARIADIC" : "ARG";
$argDefaultKind = $argInfo->hasDefaultValue() ? "_WITH_DEFAULT_VALUE" : "";
$argDefaultKind = $argInfo->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : "";
$argType = $argInfo->type;
if ($argType !== null) {
if (null !== $simpleArgType = $argType->tryToSimpleType()) {
Expand All @@ -1209,14 +1283,14 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
"\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n",
$argKind, $argDefaultKind, $argInfo->getSendByString(), $argInfo->name,
$simpleArgType->toTypeCode(), $argType->isNullable(),
$argInfo->hasDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
$argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
);
} else {
$code .= sprintf(
"\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n",
$argKind,$argDefaultKind, $argInfo->getSendByString(), $argInfo->name,
$simpleArgType->toEscapedName(), $argType->isNullable(),
$argInfo->hasDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
$argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
);
}
} else if (null !== $representableType = $argType->tryToRepresentableType()) {
Expand All @@ -1243,7 +1317,7 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
$code .= sprintf(
"\tZEND_%s_INFO%s(%s, %s%s)\n",
$argKind, $argDefaultKind, $argInfo->getSendByString(), $argInfo->name,
$argInfo->hasDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
$argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
);
}
}
Expand Down Expand Up @@ -1355,7 +1429,6 @@ function generateFunctionEntries(?Name $className, array $funcInfos): string {
return $code;
}


/**
* @return array<string, string>
*/
Expand Down Expand Up @@ -1387,15 +1460,28 @@ function generateMethodsynopses(FileInfo $fileInfo): array {

function generateMethodsynopsis(DOMDocument $doc, FuncInfo $funcInfo): DOMElement {
$methodsynopsis = $doc->createElement('methodsynopsis');
$typeString = $funcInfo->return->getTypeAsMethodsynopsisString();

foreach ($funcInfo->getModifierNames() as $modifierString) {
$modifierElement = $doc->createElement('modifier', $modifierString);
$methodsynopsis->appendChild($modifierElement);
}

$type = $doc->createElement('type', $typeString);
$methodsynopsis->appendChild($type);
$types = $funcInfo->return->getMethodsynopsisType();
if (count($types) > 1) {
$typeElement = $doc->createElement('type');
$classAttribute = $doc->createAttribute('class');
$classAttribute->value = 'union';
$typeElement->appendChild($classAttribute);

foreach ($types as $type) {
$unionTypeElement = $doc->createElement('type', $type->name);
$typeElement->appendChild($unionTypeElement);
}
} else {
$typeElement = $doc->createElement('type', $types[0]->name);
}

$methodsynopsis->appendChild($typeElement);

$methodname = $doc->createElement('methodname', $funcInfo->name->__toString());
$methodsynopsis->appendChild($methodname);
Expand All @@ -1411,14 +1497,34 @@ function generateMethodsynopsis(DOMDocument $doc, FuncInfo $funcInfo): DOMElemen
$methodparam->appendChild($choice);
}
$methodsynopsis->appendChild($methodparam);
$type = $doc->createElement('type', $arg->getTypeAsMethodsynopsisString());
$methodparam->appendChild($type);

$types = $arg->getMethodsynopsisType();
if (count($types) > 1) {
$typeElement = $doc->createElement('type');
$classAttribute = $doc->createAttribute('class');
$classAttribute->value = 'union';
$typeElement->appendChild($classAttribute);

foreach ($types as $type) {
$unionTypeElement = $doc->createElement('type', $type->name);
$typeElement->appendChild($unionTypeElement);
}
} else {
$typeElement = $doc->createElement('type', $types[0]->name);
}

$methodparam->appendChild($typeElement);
$parameter = $doc->createElement('parameter', $arg->name);
if ($arg->sendBy !== ArgInfo::SEND_BY_VAL) {
$role = $doc->createAttribute('role');
$role->value = "reference";
$parameter->appendChild($role);
}
if ($arg->isVariadic) {
$variadic = $doc->createAttribute('rep');
$variadic->value = "repeat";
$parameter->appendChild($variadic);
}
$methodparam->appendChild($parameter);
$defaultValue = $arg->getDefaultValueAsMethodsynopsisString();
if ($defaultValue !== null) {
Expand Down
2 changes: 1 addition & 1 deletion ext/bcmath/methodsynopses/bcadd.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modifier>public</modifier><type>string</type><methodname>bcadd</methodname>
<methodparam><type>string</type><parameter>num1</parameter></methodparam>
<methodparam><type>string</type><parameter>num2</parameter></methodparam>
<methodparam choice="opt"><type>int|null</type><parameter>scale</parameter><initializer>&null;</initializer></methodparam>
<methodparam choice="opt"><type class="union"><type>int</type><type>null</type></type><parameter>scale</parameter><initializer>&null;</initializer></methodparam>
</methodsynopsis>

</methodsynopses>
2 changes: 1 addition & 1 deletion ext/bcmath/methodsynopses/bccomp.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modifier>public</modifier><type>int</type><methodname>bccomp</methodname>
<methodparam><type>string</type><parameter>num1</parameter></methodparam>
<methodparam><type>string</type><parameter>num2</parameter></methodparam>
<methodparam choice="opt"><type>int|null</type><parameter>scale</parameter><initializer>&null;</initializer></methodparam>
<methodparam choice="opt"><type class="union"><type>int</type><type>null</type></type><parameter>scale</parameter><initializer>&null;</initializer></methodparam>
</methodsynopsis>

</methodsynopses>
Loading

0 comments on commit f8b2d2b

Please sign in to comment.