diff options
Diffstat (limited to 'build')
-rw-r--r-- | build/Makefile.global | 6 | ||||
-rwxr-xr-x | build/gen_stub.php | 723 | ||||
-rw-r--r-- | build/php.m4 | 6 |
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) |