summaryrefslogtreecommitdiff
path: root/build
diff options
context:
space:
mode:
Diffstat (limited to 'build')
-rw-r--r--build/Makefile.global6
-rwxr-xr-xbuild/gen_stub.php723
-rw-r--r--build/php.m46
3 files changed, 606 insertions, 129 deletions
diff --git a/build/Makefile.global b/build/Makefile.global
index 6566d052de..9a8779d56d 100644
--- a/build/Makefile.global
+++ b/build/Makefile.global
@@ -111,7 +111,7 @@ test: all
clean:
find . -name \*.gcno -o -name \*.gcda | xargs rm -f
- find . -name \*.lo -o -name \*.o | xargs rm -f
+ find . -name \*.lo -o -name \*.o -o -name \*.dep | xargs rm -f
find . -name \*.la -o -name \*.a | xargs rm -f
find . -name \*.so | xargs rm -f
find . -name .libs -a -type d|xargs rm -rf
@@ -156,9 +156,5 @@ prof-use:
fi; \
fi;
-# As we don't track includes, this is just a heuristic
-%.c: %_arginfo.h
- @touch $@
-
.PHONY: all clean install distclean test prof-gen prof-clean prof-use
.NOEXPORT:
diff --git a/build/gen_stub.php b/build/gen_stub.php
index d84851091b..084e142e34 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -2,11 +2,13 @@
<?php declare(strict_types=1);
use PhpParser\Comment\Doc as DocComment;
+use PhpParser\ConstExprEvaluator;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
+use PhpParser\Node\Stmt\Interface_;
use PhpParser\PrettyPrinter\Standard;
use PhpParser\PrettyPrinterAbstract;
@@ -54,6 +56,7 @@ function processStubFile(string $stubFile, Context $context): ?FileInfo {
initPhpParser();
$fileInfo = parseStubFile($stubCode);
+
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($arginfoFile, $arginfoCode)) {
echo "Saved $arginfoFile\n";
@@ -63,11 +66,15 @@ function processStubFile(string $stubFile, Context $context): ?FileInfo {
foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
$funcInfo->discardInfoForOldPhpVersions();
}
+ foreach ($fileInfo->getAllPropertyInfos() as $propertyInfo) {
+ $propertyInfo->discardInfoForOldPhpVersions();
+ }
+
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($legacyFile, $arginfoCode)) {
echo "Saved $legacyFile\n";
}
- }
+ }
return $fileInfo;
} catch (Exception $e) {
@@ -293,7 +300,7 @@ class Type {
return null;
}
- public function toArginfoType(): ?ArginfoType {
+ public function toArginfoType(): ArginfoType {
$classTypes = [];
$builtinTypes = [];
foreach ($this->types as $type) {
@@ -457,6 +464,24 @@ class ArgInfo {
}
}
+class PropertyName {
+ /** @var Name */
+ public $class;
+ /** @var string */
+ public $property;
+
+ public function __construct(Name $class, string $property)
+ {
+ $this->class = $class;
+ $this->property = $property;
+ }
+
+ public function __toString()
+ {
+ return $this->class->toString() . "::$" . $this->property;
+ }
+}
+
interface FunctionOrMethodName {
public function getDeclaration(): string;
public function getArgInfoName(): string;
@@ -805,6 +830,14 @@ class FuncInfo {
}
}
+ public function discardInfoForOldPhpVersions(): void {
+ $this->return->type = null;
+ foreach ($this->args as $arg) {
+ $arg->type = null;
+ $arg->defaultValue = null;
+ }
+ }
+
private function getFlagsAsArginfoString(): string
{
$flags = "ZEND_ACC_PUBLIC";
@@ -941,14 +974,6 @@ class FuncInfo {
return $methodSynopsis;
}
- public function discardInfoForOldPhpVersions(): void {
- $this->return->type = null;
- foreach ($this->args as $arg) {
- $arg->type = null;
- $arg->defaultValue = null;
- }
- }
-
private function appendMethodSynopsisTypeToElement(DOMDocument $doc, DOMElement $elementToAppend, Type $type) {
if (count($type->types) > 1) {
$typeElement = $doc->createElement('type');
@@ -966,16 +991,321 @@ class FuncInfo {
}
}
+class PropertyInfo
+{
+ /** @var PropertyName */
+ public $name;
+ /** @var int */
+ public $flags;
+ /** @var Type|null */
+ public $type;
+ /** @var Expr|null */
+ public $defaultValue;
+
+ public function __construct(PropertyName $name, int $flags, ?Type $type, ?Expr $defaultValue)
+ {
+ $this->name = $name;
+ $this->flags = $flags;
+ $this->type = $type;
+ $this->defaultValue = $defaultValue;
+ }
+
+ public function discardInfoForOldPhpVersions(): void {
+ $this->type = null;
+ }
+
+ public function getDeclaration(): string {
+ $code = "\n";
+
+ $propertyName = $this->name->property;
+
+ $defaultValueConstant = false;
+ if ($this->defaultValue === null) {
+ $defaultValue = null;
+ $defaultValueType = "undefined";
+ } else {
+ $evaluator = new ConstExprEvaluator(
+ function (Expr $expr) use (&$defaultValueConstant) {
+ if ($expr instanceof Expr\ConstFetch) {
+ $defaultValueConstant = true;
+ return null;
+ }
+
+ throw new Exception("Property $this->name has an unsupported default value");
+ }
+ );
+ $defaultValue = $evaluator->evaluateDirectly($this->defaultValue);
+ $defaultValueType = gettype($defaultValue);
+ }
+
+ if ($defaultValueConstant) {
+ echo "Skipping code generation for property $this->name, because it has a constant default value\n";
+ return "";
+ }
+
+ $typeCode = "";
+ if ($this->type) {
+ $arginfoType = $this->type->toArginfoType();
+ if ($arginfoType->hasClassType()) {
+ if (count($arginfoType->classTypes) >= 2) {
+ foreach ($arginfoType->classTypes as $classType) {
+ $className = $classType->name;
+ $code .= "\tzend_string *property_{$propertyName}_class_{$className} = zend_string_init(\"$className\", sizeof(\"$className\") - 1, 1);\n";
+ }
+
+ $classTypeCount = count($arginfoType->classTypes);
+ $code .= "\tzend_type_list *property_{$propertyName}_type_list = malloc(ZEND_TYPE_LIST_SIZE($classTypeCount));\n";
+ $code .= "\tproperty_{$propertyName}_type_list->num_types = $classTypeCount;\n";
+
+ foreach ($arginfoType->classTypes as $k => $classType) {
+ $className = $classType->name;
+ $code .= "\tproperty_{$propertyName}_type_list->types[$k] = (zend_type) ZEND_TYPE_INIT_CLASS(property_{$propertyName}_class_{$className}, 0, 0);\n";
+ }
+
+ $typeMaskCode = $this->type->toArginfoType()->toTypeMask();
+
+ $code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_PTR(property_{$propertyName}_type_list, _ZEND_TYPE_LIST_BIT, 0, $typeMaskCode);\n";
+ $typeCode = "property_{$propertyName}_type";
+ } else {
+ $className = $arginfoType->classTypes[0]->name;
+ $code .= "\tzend_string *property_{$propertyName}_class_{$className} = zend_string_init(\"$className\", sizeof(\"$className\")-1, 1);\n";
+
+ $typeCode = "(zend_type) ZEND_TYPE_INIT_CLASS(property_{$propertyName}_class_{$className}, 0, " . $arginfoType->toTypeMask() . ")";
+ }
+ } else {
+ $typeCode = "(zend_type) ZEND_TYPE_INIT_MASK(" . $arginfoType->toTypeMask() . ")";
+ }
+ }
+
+ $code .= $this->initializeValue($defaultValueType, $defaultValue, $this->type !== null);
+
+ $code .= "\tzend_string *property_{$propertyName}_name = zend_string_init(\"$propertyName\", sizeof(\"$propertyName\") - 1, 1);\n";
+ $nameCode = "property_{$propertyName}_name";
+
+ if ($this->type !== null) {
+ $code .= "\tzend_declare_typed_property(class_entry, $nameCode, &property_{$propertyName}_default_value, " . $this->getFlagsAsString() . ", NULL, $typeCode);\n";
+ } else {
+ $code .= "\tzend_declare_property_ex(class_entry, $nameCode, &property_{$propertyName}_default_value, " . $this->getFlagsAsString() . ", NULL);\n";
+ }
+ $code .= "\tzend_string_release(property_{$propertyName}_name);\n";
+
+ return $code;
+ }
+
+ /**
+ * @param mixed $value
+ */
+ private function initializeValue(string $type, $value, bool $isTyped): string
+ {
+ $name = $this->name->property;
+ $zvalName = "property_{$name}_default_value";
+
+ $code = "\tzval $zvalName;\n";
+
+ switch ($type) {
+ case "undefined":
+ if ($isTyped) {
+ $code .= "\tZVAL_UNDEF(&$zvalName);\n";
+ } else {
+ $code .= "\tZVAL_NULL(&$zvalName);\n";
+ }
+ break;
+
+ case "NULL":
+ $code .= "\tZVAL_NULL(&$zvalName);\n";
+ break;
+
+ case "boolean":
+ $code .= "\tZVAL_BOOL(&$zvalName, " . ((int) $value) . ");\n";
+ break;
+
+ case "integer":
+ $code .= "\tZVAL_LONG(&$zvalName, $value);\n";
+ break;
+
+ case "double":
+ $code .= "\tZVAL_DOUBLE(&$zvalName, $value);\n";
+ break;
+
+ case "string":
+ if ($value === "") {
+ $code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n";
+ } else {
+ $code .= "\tzend_string *{$zvalName}_str = zend_string_init(\"$value\", sizeof(\"$value\") - 1, 1);\n";
+ $code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n";
+ }
+ break;
+
+ case "array":
+ if (empty($value)) {
+ $code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n";
+ } else {
+ throw new Exception("Unimplemented property default value");
+ }
+ break;
+
+ default:
+ throw new Exception("Invalid property default value");
+ }
+
+ return $code;
+ }
+
+ private function getFlagsAsString(): string
+ {
+ $flags = "ZEND_ACC_PUBLIC";
+ if ($this->flags & Class_::MODIFIER_PROTECTED) {
+ $flags = "ZEND_ACC_PROTECTED";
+ } elseif ($this->flags & Class_::MODIFIER_PRIVATE) {
+ $flags = "ZEND_ACC_PRIVATE";
+ }
+
+ if ($this->flags & Class_::MODIFIER_STATIC) {
+ $flags .= "|ZEND_ACC_STATIC";
+ }
+
+ return $flags;
+ }
+}
+
class ClassInfo {
/** @var Name */
public $name;
+ /** @var int */
+ public $flags;
+ /** @var string */
+ public $type;
+ /** @var string|null */
+ public $alias;
+ /** @var bool */
+ public $isDeprecated;
+ /** @var bool */
+ public $isStrictProperties;
+ /** @var Name[] */
+ public $extends;
+ /** @var Name[] */
+ public $implements;
+ /** @var PropertyInfo[] */
+ public $propertyInfos;
/** @var FuncInfo[] */
public $funcInfos;
- public function __construct(Name $name, array $funcInfos) {
+ /**
+ * @param Name[] $extends
+ * @param Name[] $implements
+ * @param PropertyInfo[] $propertyInfos
+ * @param FuncInfo[] $funcInfos
+ */
+ public function __construct(
+ Name $name,
+ int $flags,
+ string $type,
+ ?string $alias,
+ bool $isDeprecated,
+ bool $isStrictProperties,
+ array $extends,
+ array $implements,
+ array $propertyInfos,
+ array $funcInfos
+ ) {
$this->name = $name;
+ $this->flags = $flags;
+ $this->type = $type;
+ $this->alias = $alias;
+ $this->isDeprecated = $isDeprecated;
+ $this->isStrictProperties = $isStrictProperties;
+ $this->extends = $extends;
+ $this->implements = $implements;
+ $this->propertyInfos = $propertyInfos;
$this->funcInfos = $funcInfos;
}
+
+ public function getRegistration(): string
+ {
+ $params = [];
+ foreach ($this->extends as $extends) {
+ $params[] = "zend_class_entry *class_entry_" . implode("_", $extends->parts);
+ }
+ foreach ($this->implements as $implements) {
+ $params[] = "zend_class_entry *class_entry_" . implode("_", $implements->parts);
+ }
+
+ $escapedName = implode("_", $this->name->parts);
+
+ $code = "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n";
+
+ $code .= "{\n";
+ $code .= "\tzend_class_entry ce, *class_entry;\n\n";
+ if (count($this->name->parts) > 1) {
+ $className = $this->name->getLast();
+ $namespace = addslashes((string) $this->name->slice(0, -1));
+
+ $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n";
+ } else {
+ $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n";
+ }
+
+ if ($this->type === "class" || $this->type === "trait") {
+ $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n";
+ } else {
+ $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n";
+ }
+ if ($this->getFlagsAsString()) {
+ $code .= "\tclass_entry->ce_flags |= " . $this->getFlagsAsString() . ";\n";
+ }
+
+ $implements = array_map(
+ function (Name $item) {
+ return "class_entry_" . implode("_", $item->parts);
+ },
+ $this->type === "interface" ? $this->extends : $this->implements
+ );
+
+ if (!empty($implements)) {
+ $code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n";
+ }
+
+ if ($this->alias) {
+ $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "_", $this->alias) . "\", class_entry);\n";
+ }
+
+ foreach ($this->propertyInfos as $property) {
+ $code .= $property->getDeclaration();
+ }
+
+ $code .= "\n\treturn class_entry;\n";
+
+ $code .= "}\n";
+
+ return $code;
+ }
+
+ private function getFlagsAsString(): string
+ {
+ $flags = [];
+
+ if ($this->type === "trait") {
+ $flags[] = "ZEND_ACC_TRAIT";
+ }
+
+ if ($this->flags & Class_::MODIFIER_FINAL) {
+ $flags[] = "ZEND_ACC_FINAL";
+ }
+
+ if ($this->flags & Class_::MODIFIER_ABSTRACT) {
+ $flags[] = "ZEND_ACC_ABSTRACT";
+ }
+
+ if ($this->isDeprecated) {
+ $flags[] = "ZEND_ACC_DEPRECATED";
+ }
+
+ if ($this->isStrictProperties) {
+ $flags[] = "ZEND_ACC_NO_DYNAMIC_PROPERTIES";
+ }
+
+ return implode("|", $flags);
+ }
}
class FileInfo {
@@ -989,6 +1319,8 @@ class FileInfo {
public $declarationPrefix = "";
/** @var bool */
public $generateLegacyArginfo = false;
+ /** @var bool */
+ public $generateClassEntries = false;
/**
* @return iterable<FuncInfo>
@@ -999,6 +1331,15 @@ class FileInfo {
yield from $classInfo->funcInfos;
}
}
+
+ /**
+ * @return iterable<PropertyInfo>
+ */
+ public function getAllPropertyInfos(): iterable {
+ foreach ($this->classInfos as $classInfo) {
+ yield from $classInfo->propertyInfos;
+ }
+ }
}
class DocCommentTag {
@@ -1028,7 +1369,7 @@ class DocCommentTag {
if ($this->name === "param") {
preg_match('/^\s*([\w\|\\\\\[\]]+)\s*\$\w+.*$/', $value, $matches);
} elseif ($this->name === "return") {
- preg_match('/^\s*([\w\|\\\\\[\]]+)\s*$/', $value, $matches);
+ preg_match('/^\s*([\w\|\\\\\[\]]+)(\s+|$)/', $value, $matches);
}
if (isset($matches[1]) === false) {
@@ -1082,128 +1423,223 @@ function parseFunctionLike(
Node\FunctionLike $func,
?string $cond
): FuncInfo {
- $comment = $func->getDocComment();
- $paramMeta = [];
- $aliasType = null;
- $alias = null;
- $isDeprecated = false;
- $verify = true;
- $docReturnType = null;
- $docParamTypes = [];
-
- if ($comment) {
- $tags = parseDocComment($comment);
- foreach ($tags as $tag) {
- if ($tag->name === 'prefer-ref') {
- $varName = $tag->getVariableName();
- if (!isset($paramMeta[$varName])) {
- $paramMeta[$varName] = [];
- }
- $paramMeta[$varName]['preferRef'] = true;
- } else if ($tag->name === 'alias' || $tag->name === 'implementation-alias') {
- $aliasType = $tag->name;
- $aliasParts = explode("::", $tag->getValue());
- if (count($aliasParts) === 1) {
- $alias = new FunctionName(new Name($aliasParts[0]));
- } else {
- $alias = new MethodName(new Name($aliasParts[0]), $aliasParts[1]);
+ try {
+ $comment = $func->getDocComment();
+ $paramMeta = [];
+ $aliasType = null;
+ $alias = null;
+ $isDeprecated = false;
+ $verify = true;
+ $docReturnType = null;
+ $docParamTypes = [];
+
+ if ($comment) {
+ $tags = parseDocComment($comment);
+ foreach ($tags as $tag) {
+ if ($tag->name === 'prefer-ref') {
+ $varName = $tag->getVariableName();
+ if (!isset($paramMeta[$varName])) {
+ $paramMeta[$varName] = [];
+ }
+ $paramMeta[$varName]['preferRef'] = true;
+ } else if ($tag->name === 'alias' || $tag->name === 'implementation-alias') {
+ $aliasType = $tag->name;
+ $aliasParts = explode("::", $tag->getValue());
+ if (count($aliasParts) === 1) {
+ $alias = new FunctionName(new Name($aliasParts[0]));
+ } else {
+ $alias = new MethodName(new Name($aliasParts[0]), $aliasParts[1]);
+ }
+ } else if ($tag->name === 'deprecated') {
+ $isDeprecated = true;
+ } else if ($tag->name === 'no-verify') {
+ $verify = false;
+ } else if ($tag->name === 'return') {
+ $docReturnType = $tag->getType();
+ } else if ($tag->name === 'param') {
+ $docParamTypes[$tag->getVariableName()] = $tag->getType();
}
- } else if ($tag->name === 'deprecated') {
- $isDeprecated = true;
- } else if ($tag->name === 'no-verify') {
- $verify = false;
- } else if ($tag->name === 'return') {
- $docReturnType = $tag->getType();
- } else if ($tag->name === 'param') {
- $docParamTypes[$tag->getVariableName()] = $tag->getType();
}
}
- }
- $varNameSet = [];
- $args = [];
- $numRequiredArgs = 0;
- $foundVariadic = false;
- foreach ($func->getParams() as $i => $param) {
- $varName = $param->var->name;
- $preferRef = !empty($paramMeta[$varName]['preferRef']);
- unset($paramMeta[$varName]);
+ $varNameSet = [];
+ $args = [];
+ $numRequiredArgs = 0;
+ $foundVariadic = false;
+ foreach ($func->getParams() as $i => $param) {
+ $varName = $param->var->name;
+ $preferRef = !empty($paramMeta[$varName]['preferRef']);
+ unset($paramMeta[$varName]);
- if (isset($varNameSet[$varName])) {
- throw new Exception("Duplicate parameter name $varName for function $name");
- }
- $varNameSet[$varName] = true;
+ if (isset($varNameSet[$varName])) {
+ throw new Exception("Duplicate parameter name $varName");
+ }
+ $varNameSet[$varName] = true;
- if ($preferRef) {
- $sendBy = ArgInfo::SEND_PREFER_REF;
- } else if ($param->byRef) {
- $sendBy = ArgInfo::SEND_BY_REF;
- } else {
- $sendBy = ArgInfo::SEND_BY_VAL;
- }
+ if ($preferRef) {
+ $sendBy = ArgInfo::SEND_PREFER_REF;
+ } else if ($param->byRef) {
+ $sendBy = ArgInfo::SEND_BY_REF;
+ } else {
+ $sendBy = ArgInfo::SEND_BY_VAL;
+ }
+
+ if ($foundVariadic) {
+ throw new Exception("Only the last parameter can be variadic");
+ }
+
+ $type = $param->type ? Type::fromNode($param->type) : null;
+ if ($type === null && !isset($docParamTypes[$varName])) {
+ throw new Exception("Missing parameter type");
+ }
+
+ if ($param->default instanceof Expr\ConstFetch &&
+ $param->default->name->toLowerString() === "null" &&
+ $type && !$type->isNullable()
+ ) {
+ $simpleType = $type->tryToSimpleType();
+ if ($simpleType === null) {
+ throw new Exception("Parameter $varName has null default, but is not nullable");
+ }
+ }
+
+ $foundVariadic = $param->variadic;
- if ($foundVariadic) {
- throw new Exception("Error in function $name: only the last parameter can be variadic");
+ $args[] = new ArgInfo(
+ $varName,
+ $sendBy,
+ $param->variadic,
+ $type,
+ isset($docParamTypes[$varName]) ? Type::fromPhpDoc($docParamTypes[$varName]) : null,
+ $param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null
+ );
+ if (!$param->default && !$param->variadic) {
+ $numRequiredArgs = $i + 1;
+ }
}
- $type = $param->type ? Type::fromNode($param->type) : null;
- if ($type === null && !isset($docParamTypes[$varName])) {
- throw new Exception("Missing parameter type for function $name()");
+ foreach (array_keys($paramMeta) as $var) {
+ throw new Exception("Found metadata for invalid param $var");
}
- if ($param->default instanceof Expr\ConstFetch &&
- $param->default->name->toLowerString() === "null" &&
- $type && !$type->isNullable()
- ) {
- $simpleType = $type->tryToSimpleType();
- if ($simpleType === null) {
- throw new Exception(
- "Parameter $varName of function $name has null default, but is not nullable");
- }
+ $returnType = $func->getReturnType();
+ if ($returnType === null && $docReturnType === null && !$name->isConstructor() && !$name->isDestructor()) {
+ throw new Exception("Missing return type");
}
- $foundVariadic = $param->variadic;
+ $return = new ReturnInfo(
+ $func->returnsByRef(),
+ $returnType ? Type::fromNode($returnType) : null,
+ $docReturnType ? Type::fromPhpDoc($docReturnType) : null
+ );
- $args[] = new ArgInfo(
- $varName,
- $sendBy,
- $param->variadic,
- $type,
- isset($docParamTypes[$varName]) ? Type::fromPhpDoc($docParamTypes[$varName]) : null,
- $param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null
+ return new FuncInfo(
+ $name,
+ $classFlags,
+ $flags,
+ $aliasType,
+ $alias,
+ $isDeprecated,
+ $verify,
+ $args,
+ $return,
+ $numRequiredArgs,
+ $cond
);
- if (!$param->default && !$param->variadic) {
- $numRequiredArgs = $i + 1;
+ } catch (Exception $e) {
+ throw new Exception($name . "(): " .$e->getMessage());
+ }
+}
+
+function parseProperty(
+ Name $class,
+ int $flags,
+ Stmt\PropertyProperty $property,
+ ?Node $type,
+ ?DocComment $comment
+): PropertyInfo {
+ $docType = false;
+
+ if ($comment) {
+ $tags = parseDocComment($comment);
+ foreach ($tags as $tag) {
+ if ($tag->name === 'var') {
+ $docType = true;
+ }
}
}
- foreach (array_keys($paramMeta) as $var) {
- throw new Exception("Found metadata for invalid param $var of function $name");
+ $propertyType = $type ? Type::fromNode($type) : null;
+ if ($propertyType === null && !$docType) {
+ throw new Exception("Missing type for property $class::\$$property->name");
}
- $returnType = $func->getReturnType();
- if ($returnType === null && $docReturnType === null && !$name->isConstructor() && !$name->isDestructor()) {
- throw new Exception("Missing return type for function $name()");
+ if ($property->default instanceof Expr\ConstFetch &&
+ $property->default->name->toLowerString() === "null" &&
+ $propertyType && !$propertyType->isNullable()
+ ) {
+ $simpleType = $propertyType->tryToSimpleType();
+ if ($simpleType === null) {
+ throw new Exception(
+ "Property $class::\$$property->name has null default, but is not nullable");
+ }
}
- $return = new ReturnInfo(
- $func->returnsByRef(),
- $returnType ? Type::fromNode($returnType) : null,
- $docReturnType ? Type::fromPhpDoc($docReturnType) : null
+ return new PropertyInfo(
+ new PropertyName($class, $property->name->__toString()),
+ $flags,
+ $propertyType,
+ $property->default
);
+}
+
+/**
+ * @param PropertyInfo[] $properties
+ * @param FuncInfo[] $methods
+ */
+function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $methods): ClassInfo {
+ $flags = $class instanceof Class_ ? $class->flags : 0;
+ $comment = $class->getDocComment();
+ $alias = null;
+ $isDeprecated = false;
+ $isStrictProperties = false;
- return new FuncInfo(
+ if ($comment) {
+ $tags = parseDocComment($comment);
+ foreach ($tags as $tag) {
+ if ($tag->name === 'alias') {
+ $alias = $tag->getValue();
+ } else if ($tag->name === 'deprecated') {
+ $isDeprecated = true;
+ } else if ($tag->name === 'strict-properties') {
+ $isStrictProperties = true;
+ }
+ }
+ }
+
+ $extends = [];
+ $implements = [];
+
+ if ($class instanceof Class_) {
+ if ($class->extends) {
+ $extends[] = $class->extends;
+ }
+ $implements = $class->implements;
+ } elseif ($class instanceof Interface_) {
+ $extends = $class->extends;
+ }
+
+ return new ClassInfo(
$name,
- $classFlags,
$flags,
- $aliasType,
+ $class instanceof Class_ ? "class" : ($class instanceof Interface_ ? "interface" : "trait"),
$alias,
$isDeprecated,
- $verify,
- $args,
- $return,
- $numRequiredArgs,
- $cond
+ $isStrictProperties,
+ $extends,
+ $implements,
+ $properties,
+ $methods
);
}
@@ -1279,6 +1715,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
if ($stmt instanceof Stmt\ClassLike) {
$className = $stmt->namespacedName;
+ $propertyInfos = [];
$methodInfos = [];
foreach ($stmt->stmts as $classStmt) {
$cond = handlePreprocessorConditions($conds, $classStmt);
@@ -1286,7 +1723,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
continue;
}
- if (!$classStmt instanceof Stmt\ClassMethod) {
+ if (!$classStmt instanceof Stmt\ClassMethod && !$classStmt instanceof Stmt\Property) {
throw new Exception("Not implemented {$classStmt->getType()}");
}
@@ -1301,20 +1738,32 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
}
if (!($flags & Class_::VISIBILITY_MODIFIER_MASK)) {
- throw new Exception("Method visibility modifier is required");
+ throw new Exception("Visibility modifier is required");
}
- $methodInfos[] = parseFunctionLike(
- $prettyPrinter,
- new MethodName($className, $classStmt->name->toString()),
- $classFlags,
- $flags,
- $classStmt,
- $cond
- );
+ if ($classStmt instanceof Stmt\Property) {
+ foreach ($classStmt->props as $property) {
+ $propertyInfos[] = parseProperty(
+ $className,
+ $flags,
+ $property,
+ $classStmt->type,
+ $classStmt->getDocComment()
+ );
+ }
+ } else if ($classStmt instanceof Stmt\ClassMethod) {
+ $methodInfos[] = parseFunctionLike(
+ $prettyPrinter,
+ new MethodName($className, $classStmt->name->toString()),
+ $classFlags,
+ $flags,
+ $classStmt,
+ $cond
+ );
+ }
}
- $fileInfo->classInfos[] = new ClassInfo($className, $methodInfos);
+ $fileInfo->classInfos[] = parseClass($className, $stmt, $propertyInfos, $methodInfos);
continue;
}
@@ -1346,10 +1795,18 @@ function parseStubFile(string $code): FileInfo {
$fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
} else if ($tag->name === 'generate-legacy-arginfo') {
$fileInfo->generateLegacyArginfo = true;
+ } else if ($tag->name === 'generate-class-entries') {
+ $fileInfo->generateClassEntries = true;
+ $fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
}
}
}
+ // Generating class entries require generating function/method entries
+ if ($fileInfo->generateClassEntries && !$fileInfo->generateFunctionEntries) {
+ $fileInfo->generateFunctionEntries = true;
+ }
+
handleStatements($fileInfo, $stmts, $prettyPrinter);
return $fileInfo;
}
@@ -1531,6 +1988,20 @@ function generateArgInfoCode(FileInfo $fileInfo, string $stubHash): string {
}
}
+ if ($fileInfo->generateClassEntries) {
+ $code .= generateClassEntryCode($fileInfo);
+ }
+
+ return $code;
+}
+
+function generateClassEntryCode(FileInfo $fileInfo): string {
+ $code = "";
+
+ foreach ($fileInfo->classInfos as $class) {
+ $code .= "\n" . $class->getRegistration();
+ }
+
return $code;
}
@@ -1868,6 +2339,7 @@ foreach ($fileInfos as $fileInfo) {
/** @var FuncInfo $funcInfo */
$funcMap[$funcInfo->name->__toString()] = $funcInfo;
+ // TODO: Don't use aliasMap for methodsynopsis?
if ($funcInfo->aliasType === "alias") {
$aliasMap[$funcInfo->alias->__toString()] = $funcInfo;
}
@@ -1877,7 +2349,11 @@ foreach ($fileInfos as $fileInfo) {
if ($verify) {
$errors = [];
- foreach ($aliasMap as $aliasFunc) {
+ foreach ($funcMap as $aliasFunc) {
+ if (!$aliasFunc->alias) {
+ continue;
+ }
+
if (!isset($funcMap[$aliasFunc->alias->__toString()])) {
$errors[] = "Aliased function {$aliasFunc->alias}() cannot be found";
continue;
@@ -1930,11 +2406,12 @@ if ($verify) {
$aliasArgs, $aliasedArgs
);
- if ((!$aliasedFunc->isMethod() || $aliasedFunc->isFinalMethod()) &&
- (!$aliasFunc->isMethod() || $aliasFunc->isFinalMethod()) &&
- $aliasFunc->return != $aliasedFunc->return
- ) {
- $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
+ if (!$aliasedFunc->name->isConstructor() && !$aliasFunc->name->isConstructor()) {
+ $aliasedReturnType = $aliasedFunc->return->type ?? $aliasedFunc->return->phpDocType;
+ $aliasReturnType = $aliasFunc->return->type ?? $aliasFunc->return->phpDocType;
+ if ($aliasReturnType != $aliasedReturnType) {
+ $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
+ }
}
}
diff --git a/build/php.m4 b/build/php.m4
index 9746ba28f3..04651f01e2 100644
--- a/build/php.m4
+++ b/build/php.m4
@@ -257,8 +257,12 @@ dnl Choose the right compiler/flags/etc. for the source-file.
*.cpp|*.cc|*.cxx[)] ac_comp="$b_cxx_pre $ac_inc $b_cxx_meta $3 -c $ac_srcdir$ac_src -o $ac_bdir$ac_obj.$b_lo $b_cxx_post" ;;
esac
+dnl Generate Makefiles with dependencies
+ ac_comp="$ac_comp -MMD -MF $ac_bdir$ac_obj.dep -MT $ac_bdir[$]ac_obj.lo"
+
dnl Create a rule for the object/source combo.
cat >>Makefile.objects<<EOF
+-include $ac_bdir[$]ac_obj.dep
$ac_bdir[$]ac_obj.lo: $ac_srcdir[$]ac_src
$ac_comp
EOF
@@ -1903,7 +1907,7 @@ dnl
AC_DEFUN([PHP_SETUP_OPENSSL],[
found_openssl=no
- PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1], [found_openssl=yes])
+ PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.2], [found_openssl=yes])
if test "$found_openssl" = "yes"; then
PHP_EVAL_LIBLINE($OPENSSL_LIBS, $1)